noumanjavaid commited on
Commit
b7b12c8
·
verified ·
1 Parent(s): e2b3134

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +975 -405
app.py CHANGED
@@ -3,79 +3,16 @@ import base64
3
  from io import BytesIO
4
  import json
5
  from datetime import datetime
6
- import time
7
- import re
8
- import os
9
 
10
- # Set page configuration and title
11
  st.set_page_config(
12
  page_title="ResumeBuilder Pro",
13
  layout="wide",
14
  initial_sidebar_state="collapsed"
15
  )
16
 
17
- # Define a flag to check if API functionality is available
18
- if "gemini_available" not in st.session_state:
19
- st.session_state.gemini_available = False
20
-
21
- # Try to import Google Generative AI library, but make it optional
22
- try:
23
- import google.generativeai as genai
24
- st.session_state.gemini_import_success = True
25
- except ImportError:
26
- st.session_state.gemini_import_success = False
27
-
28
- # Initialize Gemini API (modified to handle missing API key gracefully)
29
- def initialize_gemini_api():
30
- if not st.session_state.gemini_import_success:
31
- st.warning("Google Generative AI library not installed. Install with: `pip install google-generativeai`")
32
- return False
33
-
34
- try:
35
- # Get API key from session state first (from text input)
36
- api_key = st.session_state.get("api_key", "")
37
-
38
- # Only try to configure if API key is provided
39
- if api_key:
40
- genai.configure(api_key=api_key)
41
- # Test the API with a simple call to verify it works
42
- model = genai.GenerativeModel(model_name="gemini-pro")
43
- _ = model.generate_content("Hello")
44
- return True
45
- else:
46
- # No API key provided yet, not an error
47
- return False
48
- except Exception as e:
49
- st.error(f"Failed to initialize Gemini API: {str(e)}")
50
- return False
51
-
52
- # Function to get Gemini model response
53
- def get_gemini_response(prompt, temperature=0.7):
54
- if not st.session_state.gemini_available:
55
- st.warning("Gemini API not available. Please set up your API key.")
56
- return None
57
-
58
- try:
59
- # Create the model
60
- generation_config = {
61
- "temperature": temperature,
62
- "top_p": 0.95,
63
- "top_k": 64,
64
- "max_output_tokens": 8192,
65
- }
66
-
67
- model = genai.GenerativeModel(
68
- model_name="gemini-pro",
69
- generation_config=generation_config,
70
- )
71
-
72
- response = model.generate_content(prompt)
73
- return response.text
74
- except Exception as e:
75
- st.error(f"Error getting AI response: {str(e)}")
76
- return None
77
-
78
- # Initialize session state for resume data
79
  if "resume_data" not in st.session_state:
80
  st.session_state.resume_data = {
81
  "fullName": "Alexander Johnson",
@@ -132,26 +69,37 @@ if "cover_letter" not in st.session_state:
132
  if "suggested_skills" not in st.session_state:
133
  st.session_state.suggested_skills = []
134
 
 
 
 
 
 
 
 
 
 
 
 
135
  # Apply custom styling based on dark/light mode
136
  def apply_custom_styling():
137
  dark_mode = st.session_state.dark_mode
138
  primary_color = "#4F46E5" # Indigo
139
 
140
  if dark_mode:
141
- background_color = "#111827" # Dark gray
142
- text_color = "#F9FAFB" # Almost white
143
- card_bg = "#1F2937" # Medium gray
144
- input_bg = "#374151" # Light gray
145
- secondary_bg = "#1E3A8A" # Dark blue
146
- accent_light = "#818CF8" # Lighter indigo
147
  else:
148
- background_color = "#F9FAFB" # Almost white
149
- text_color = "#111827" # Dark gray
150
- card_bg = "#FFFFFF" # White
151
- input_bg = "#F3F4F6" # Light gray
152
- secondary_bg = "#EEF2FF" # Light indigo
153
- accent_light = "#C7D2FE" # Very light indigo
154
-
155
  css = f"""
156
  <style>
157
  /* Base colors and fonts */
@@ -163,17 +111,60 @@ def apply_custom_styling():
163
  --input-bg: {input_bg};
164
  --secondary-bg: {secondary_bg};
165
  --accent-light: {accent_light};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  }}
167
 
168
  /* Main page styling */
169
  .main .block-container {{
170
- padding-top: 2rem;
171
- max-width: 1200px;
 
172
  }}
173
 
174
  .stApp {{
175
  background-color: var(--bg-color);
176
  color: var(--text-color);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  }}
178
 
179
  /* Header styling */
@@ -183,19 +174,62 @@ def apply_custom_styling():
183
 
184
  /* Make inputs match theme */
185
  .stTextInput > div > div > input,
186
- .stTextArea > div > div > textarea {{
 
187
  background-color: var(--input-bg);
188
  color: var(--text-color);
189
  border-radius: 0.375rem;
 
 
 
 
 
 
 
 
190
  }}
191
 
192
  /* Custom card styling */
193
  .custom-card {{
194
  background-color: var(--card-bg);
195
  border-radius: 0.5rem;
196
- padding: 1.5rem;
197
- margin-bottom: 1rem;
198
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  }}
200
 
201
  /* AI feature styling */
@@ -218,99 +252,135 @@ def apply_custom_styling():
218
 
219
  /* Resume preview styling */
220
  .resume-container {{
221
- background-color: var(--card-bg);
222
- border-radius: 0.5rem;
223
- padding: 2rem;
224
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 
 
 
 
225
  }}
226
 
 
227
  .resume-header {{
 
228
  margin-bottom: 1.5rem;
 
 
229
  }}
230
 
231
  .resume-name {{
232
- font-size: 2rem;
233
- font-weight: bold;
234
- margin-bottom: 0.25rem;
235
- color: var(--text-color);
236
  }}
237
 
238
  .resume-title {{
239
- font-size: 1.25rem;
240
- color: var(--primary-color);
 
241
  margin-bottom: 0.5rem;
242
  }}
243
 
244
  .contact-info {{
245
- font-size: 0.875rem;
246
- margin-top: 0.5rem;
247
- color: var(--text-color);
 
 
 
248
  }}
249
 
 
250
  .section-title {{
251
- font-size: 1.25rem;
252
- font-weight: 600;
 
 
253
  margin-top: 1.5rem;
254
- margin-bottom: 1rem;
255
- padding-bottom: 0.5rem;
256
- border-bottom: 1px solid {f"rgba(255,255,255,0.1)" if dark_mode else "rgba(0,0,0,0.1)"};
257
- color: var(--text-color);
258
  }}
259
 
260
- .company-name {{
261
- color: var(--primary-color);
262
- font-size: 1rem;
263
  }}
264
 
265
  .job-title {{
266
- font-weight: 600;
267
- font-size: 1.125rem;
268
- color: var(--text-color);
 
 
 
 
 
 
 
269
  }}
270
 
271
  .duration {{
272
- font-size: 0.875rem;
273
- color: {"rgba(255,255,255,0.7)" if dark_mode else "rgba(0,0,0,0.6)"};
 
274
  }}
275
 
276
  .job-description {{
 
 
277
  margin-top: 0.5rem;
278
- font-size: 0.9375rem;
279
- color: {"rgba(255,255,255,0.9)" if dark_mode else "rgba(0,0,0,0.8)"};
 
 
 
 
280
  }}
281
 
282
  .institution {{
283
- font-weight: 600;
284
- font-size: 1.125rem;
285
- color: var(--text-color);
 
286
  }}
287
 
288
  .degree {{
289
- font-size: 1rem;
290
- color: {"rgba(255,255,255,0.9)" if dark_mode else "rgba(0,0,0,0.8)"};
 
 
 
 
 
 
 
 
291
  }}
292
 
293
  .skill-tag {{
294
- display: inline-block;
295
- background-color: {f"rgba(79, 70, 229, 0.2)" if dark_mode else "rgba(79, 70, 229, 0.1)"};
296
- color: {f"rgba(255,255,255,0.9)" if dark_mode else "#4F46E5"};
297
- padding: 0.35rem 0.7rem;
298
- border-radius: 9999px;
299
- margin-right: 0.5rem;
300
- margin-bottom: 0.5rem;
301
- font-size: 0.875rem;
302
  }}
303
 
304
  .recommended-skill {{
305
  display: inline-block;
306
- background-color: {f"rgba(5, 150, 105, 0.2)" if dark_mode else "rgba(5, 150, 105, 0.1)"};
307
- color: {f"rgba(255,255,255,0.9)" if dark_mode else "#059669"};
308
  padding: 0.35rem 0.7rem;
309
  border-radius: 9999px;
310
  margin-right: 0.5rem;
311
  margin-bottom: 0.5rem;
312
  font-size: 0.875rem;
313
- border: 1px dashed {f"rgba(5, 150, 105, 0.5)" if dark_mode else "rgba(5, 150, 105, 0.5)"};
314
  }}
315
 
316
  /* Tab styling */
@@ -334,10 +404,36 @@ def apply_custom_styling():
334
  border-color: {f"rgba(255,255,255,0.1)" if dark_mode else "rgba(0,0,0,0.1)"};
335
  }}
336
 
 
 
 
 
 
 
 
 
 
 
 
337
  /* Button styling */
338
  .stButton button {{
339
  border-radius: 0.375rem;
340
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
 
342
  /* Make the buttons in header look nicer */
343
  .header-button {{
@@ -414,10 +510,12 @@ def apply_custom_styling():
414
 
415
  /* Cover letter styling */
416
  .cover-letter {{
417
- background-color: var(--card-bg);
418
- border-radius: 0.5rem;
419
- padding: 2rem;
420
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
 
 
421
  margin-top: 1rem;
422
  }}
423
 
@@ -455,6 +553,34 @@ def apply_custom_styling():
455
  border: 1px dashed var(--primary-color);
456
  text-align: center;
457
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
458
  </style>
459
  """
460
  st.markdown(css, unsafe_allow_html=True)
@@ -464,14 +590,64 @@ def download_resume_json():
464
  json_str = json.dumps(st.session_state.resume_data, indent=2)
465
  b64 = base64.b64encode(json_str.encode()).decode()
466
  filename = f"resume_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
467
- href = f'<a href="data:file/json;base64,{b64}" download="{filename}" class="header-button" style="text-decoration:none;padding:0.5rem 1rem;margin-right:0.5rem;">📥 Download JSON</a>'
468
  return href
469
 
470
  # Function to create a placeholder for PDF export
471
  def download_resume_pdf():
472
  # In a real app, you would generate an actual PDF here
473
- # For this example, we'll just return a placeholder message
474
- return '<button class="header-button" onclick="alert(\'PDF generation would be implemented here in a real app\');">📄 Export PDF</button>'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
  # Main application header
477
  def render_header():
@@ -479,7 +655,7 @@ def render_header():
479
 
480
  with col1:
481
  ai_badge = '<span style="background-color: var(--primary-color); color: white; font-size: 0.6em; padding: 0.2em 0.5em; border-radius: 0.5em; margin-left: 0.5em;">AI-Ready</span>' if st.session_state.gemini_import_success else ""
482
- st.markdown(f'<h1 style="display:flex;align-items:center;"><span style="margin-right:10px;">📝</span> ResumeBuilder Pro {ai_badge}</h1>', unsafe_allow_html=True)
483
 
484
  with col2:
485
  download_buttons = f"""
@@ -517,7 +693,7 @@ def manage_api_key():
517
 
518
  # Show different message if the generative AI library is not installed
519
  if not st.session_state.gemini_import_success:
520
- st.markdown("### 🔧 Enable AI Features")
521
  st.markdown("""
522
  To use AI features, you need to install the Google Generative AI library:
523
  ```
@@ -527,19 +703,20 @@ def manage_api_key():
527
  """)
528
  st.markdown('</div>', unsafe_allow_html=True)
529
  return
530
-
531
  # If library is installed but API key not set
532
  st.markdown("### 🔑 Gemini API Setup")
533
  st.markdown("To use AI features, enter your Google Gemini API key below:")
534
-
535
  api_key = st.text_input("API Key",
536
  value=st.session_state.api_key,
537
  type="password",
538
- placeholder="Enter your Gemini API key here")
539
-
 
540
  col1, col2 = st.columns([1, 3])
541
  with col1:
542
- if st.button("Save API Key"):
543
  st.session_state.api_key = api_key
544
  if initialize_gemini_api():
545
  st.session_state.gemini_available = True
@@ -549,14 +726,14 @@ def manage_api_key():
549
  st.error("Invalid API key. Please check and try again.")
550
  else:
551
  st.warning("Please enter an API key to enable AI features.")
552
-
553
  with col2:
554
  st.markdown("Get your API key from [Google AI Studio](https://makersuite.google.com/app/apikey)")
555
-
556
  # Show what AI features are available
557
  if not st.session_state.gemini_available:
558
  st.markdown('<div class="ai-features-banner">', unsafe_allow_html=True)
559
- st.markdown("### 🚀 Unlock AI Features")
560
  st.markdown("""
561
  By adding your API key, you'll unlock powerful AI features:
562
 
@@ -570,72 +747,68 @@ def manage_api_key():
570
 
571
  st.markdown('</div>', unsafe_allow_html=True)
572
 
573
- # AI Features - These will only be activated when API is available
574
-
575
- # Generate professional summary
576
  def generate_ai_summary():
577
  data = st.session_state.resume_data
578
-
579
- # Create prompt for summary generation
580
  prompt = f"""
581
  You are an expert resume writer. Generate a compelling professional summary for a resume with these details:
582
-
583
  Name: {data['fullName']}
584
  Current Position: {data['title']}
585
  Skills: {', '.join(data['skills'])}
586
-
587
  Experience:
588
  {' '.join([f"{exp['position']} at {exp['company']} ({exp['duration']}): {exp['description']}" for exp in data['experience']])}
589
-
590
  Education:
591
  {' '.join([f"{edu['degree']} from {edu['institution']} ({edu['duration']})" for edu in data['education']])}
592
-
593
  Rules for writing the summary:
594
  1. Keep it concise (3-4 sentences maximum)
595
  2. Highlight key skills and accomplishments
596
  3. Focus on value provided in past roles
597
  4. Use active language and avoid clichés
598
  5. Target it toward professional growth
599
-
600
  Write ONLY the summary. Don't include explanations or other text.
601
  """
602
-
603
  with st.spinner("Generating professional summary..."):
604
  summary = get_gemini_response(prompt, temperature=0.7)
605
  if summary:
606
  # Clean up the response
607
  summary = summary.strip().replace('"', '')
 
608
  return summary
609
- return None
610
 
611
- # Analyze job description and match skills
612
  def analyze_job_description(job_description):
613
  data = st.session_state.resume_data
614
-
615
  prompt = f"""
616
  You are an expert resume consultant. Analyze this job description and the candidate's resume to provide insights.
617
-
618
  JOB DESCRIPTION:
619
  {job_description}
620
-
621
  CANDIDATE RESUME:
622
  Name: {data['fullName']}
623
  Current Position: {data['title']}
624
  Skills: {', '.join(data['skills'])}
625
-
626
  Experience:
627
  {' '.join([f"{exp['position']} at {exp['company']} ({exp['duration']}): {exp['description']}" for exp in data['experience']])}
628
-
629
  Education:
630
  {' '.join([f"{edu['degree']} from {edu['institution']} ({edu['duration']})" for edu in data['education']])}
631
-
632
  Provide the following in JSON format:
633
  1. "match_percentage": A numerical estimate (0-100) of how well the candidate's skills match the job requirements
634
  2. "missing_skills": A list of 3-5 key skills mentioned in the job that are missing from the candidate's resume
635
  3. "highlight_skills": A list of skills the candidate has that are particularly relevant to this job
636
  4. "emphasis_suggestions": 2-3 specific parts of the candidate's experience that should be emphasized for this job
637
  5. "improvement_tips": 2-3 brief suggestions to improve the resume for this specific job
638
-
639
  Return ONLY the JSON, formatted as follows:
640
  {{
641
  "match_percentage": number,
@@ -645,287 +818,684 @@ def analyze_job_description(job_description):
645
  "improvement_tips": [list of strings]
646
  }}
647
  """
648
-
649
  with st.spinner("Analyzing job description..."):
650
  analysis = get_gemini_response(prompt, temperature=0.2)
651
  if analysis:
652
  try:
653
- # Extract just the JSON part from the response
654
- # First find JSON pattern using regex
655
- json_match = re.search(r'\{[\s\S]*\}', analysis)
656
- if json_match:
657
- json_str = json_match.group(0)
658
- return json.loads(json_str)
659
- return json.loads(analysis)
 
 
 
 
 
 
660
  except Exception as e:
661
  st.error(f"Error parsing AI response: {str(e)}")
662
  st.write("Raw response:", analysis)
663
- return None
 
664
 
665
- # Improve experience descriptions
666
- def improve_experience_description(description, position, company):
667
- prompt = f"""
668
- You are an expert resume writer. Enhance the following job description to be more impactful and achievement-oriented:
669
-
670
- Position: {position}
671
- Company: {company}
672
- Current Description: {description}
673
-
674
- Rewrite the description to:
675
- 1. Focus on achievements with measurable results
676
- 2. Start with strong action verbs
677
- 3. Highlight relevant skills and impact
678
- 4. Be concise but comprehensive (max 3 bullet points)
679
- 5. Use quantifiable metrics where possible
680
-
681
- Return ONLY the improved description without additional commentary.
682
- """
683
-
684
- with st.spinner("Improving description..."):
685
- improved = get_gemini_response(prompt, temperature=0.7)
686
- if improved:
687
- return improved.strip()
688
- return None
689
-
690
- # Generate cover letter
691
- def generate_cover_letter(job_description, company_name):
692
  data = st.session_state.resume_data
693
-
694
  prompt = f"""
695
- You are an expert cover letter writer. Create a personalized cover letter for:
 
 
 
696
 
697
- Applicant: {data['fullName']}
 
698
  Current Position: {data['title']}
699
  Skills: {', '.join(data['skills'])}
700
- Target Company: {company_name}
701
 
702
- Based on this job description:
703
- {job_description}
704
 
705
- Experience highlights:
706
- {' '.join([f"{exp['position']} at {exp['company']} ({exp['duration']}): {exp['description']}" for exp in data['experience'][:2]])}
707
 
708
- Cover letter guidelines:
709
- 1. Keep it under 250 words
710
- 2. Include a personalized greeting
711
- 3. Start with an engaging opening that shows enthusiasm
712
- 4. Connect 2-3 of the applicant's skills/experiences directly to the job requirements
713
- 5. Include a specific reason why the applicant wants to work at this company
714
- 6. End with a call to action
715
- 7. Use a professional closing
716
 
717
- Format it as a proper cover letter with date, greeting, paragraphs, and signature.
718
- Return ONLY the cover letter, no explanations or additional notes.
719
  """
720
 
721
  with st.spinner("Generating cover letter..."):
722
  cover_letter = get_gemini_response(prompt, temperature=0.7)
723
  if cover_letter:
724
  return cover_letter.strip()
725
- return None
726
 
727
- # Suggest skills based on job title
728
- def suggest_skills_for_role(job_title):
729
- prompt = f"""
730
- You are a career advisor specializing in resume building. Generate a list of 8-10 highly relevant technical and soft skills for someone in this role:
731
-
732
- Job Title: {job_title}
733
-
734
- Return the skills as a JSON array of strings. ONLY return the JSON array, no other text.
735
- Example: ["Skill 1", "Skill 2", "Skill 3"]
736
- """
737
-
738
- with st.spinner("Suggesting skills..."):
739
- skills_json = get_gemini_response(prompt, temperature=0.3)
740
- if skills_json:
741
- try:
742
- # Clean up and parse the response
743
- skills_json = skills_json.strip()
744
- if skills_json.startswith('```') and skills_json.endswith('```'):
745
- skills_json = skills_json[3:-3].strip()
746
- if skills_json.startswith('json'):
747
- skills_json = skills_json[4:].strip()
748
- return json.loads(skills_json)
749
- except Exception as e:
750
- st.error(f"Error parsing skills: {str(e)}")
751
- return []
752
- # Basic Info tab with AI enhancement
753
- def render_basic_info():
754
- data = st.session_state.resume_data
755
 
756
- st.markdown('<div class="custom-card">', unsafe_allow_html=True)
757
- st.subheader("Personal Information")
 
 
 
 
 
758
 
759
- col1, col2 = st.columns(2)
760
- with col1:
761
- new_name = st.text_input("Full Name", value=data["fullName"], key="fullName")
762
- if new_name != data["fullName"]:
763
- st.session_state.resume_data["fullName"] = new_name
 
 
 
 
 
 
764
 
765
- new_email = st.text_input("Email", value=data["email"], key="email")
766
- if new_email != data["email"]:
767
- st.session_state.resume_data["email"] = new_email
768
 
769
- new_location = st.text_input("Location", value=data["location"], key="location")
770
- if new_location != data["location"]:
771
- st.session_state.resume_data["location"] = new_location
772
-
773
- with col2:
774
- new_title = st.text_input("Professional Title", value=data["title"], key="title")
775
- if new_title != data["title"]:
776
- st.session_state.resume_data["title"] = new_title
777
 
778
- new_phone = st.text_input("Phone", value=data["phone"], key="phone")
779
- if new_phone != data["phone"]:
780
- st.session_state.resume_data["phone"] = new_phone
781
-
782
- st.markdown("### Professional Summary")
783
-
784
- # Add AI summary generation if API key is set
785
- if st.session_state.gemini_available:
786
- col1, col2 = st.columns([3, 1])
787
- with col2:
788
- if st.button("✨ Generate AI Summary"):
789
- summary = generate_ai_summary()
790
- if summary:
791
- st.session_state.resume_data["summary"] = summary
792
- st.rerun()
793
-
794
- new_summary = st.text_area("", value=data["summary"], height=150, key="summary")
795
- if new_summary != data["summary"]:
796
- st.session_state.resume_data["summary"] = new_summary
797
-
798
- st.markdown('</div>', unsafe_allow_html=True)
799
-
800
- # Add Job Description Analysis section if API key is set
801
- if st.session_state.gemini_available:
802
- st.markdown('<div class="ai-container">', unsafe_allow_html=True)
803
- st.markdown("### 🔍 Job Match Analysis")
804
- st.markdown("Paste a job description to get AI-powered insights on how your resume matches the requirements.")
805
-
806
- job_description = st.text_area("Job Description", height=100, key="job_desc_input",
807
- value=st.session_state.job_description)
808
-
809
- if job_description:
810
- company_name = st.text_input("Company Name (for cover letter)", key="company_name")
811
 
812
- col1, col2, col3 = st.columns([1,1,1])
813
- with col1:
814
- if st.button("Analyze Job Match"):
815
- st.session_state.job_description = job_description
816
- analysis = analyze_job_description(job_description)
817
- if analysis:
818
- st.session_state.ai_suggestions = analysis
819
- st.rerun()
820
 
821
- with col2:
822
- if st.button("Generate Cover Letter") and company_name:
823
- st.session_state.job_description = job_description
824
- cover_letter = generate_cover_letter(job_description, company_name)
825
- if cover_letter:
826
- st.session_state.cover_letter = cover_letter
827
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
828
 
829
- with col3:
830
- if st.button("Suggest Skills"):
831
- suggested_skills = suggest_skills_for_role(data["title"])
832
- if suggested_skills:
833
- st.session_state.suggested_skills = suggested_skills
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
834
  st.rerun()
835
-
836
- # Display analysis results if available
837
- if st.session_state.ai_suggestions:
838
- analysis = st.session_state.ai_suggestions
839
 
840
- st.markdown("### 📊 Job Match Analysis Results")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
841
 
842
- # Match percentage
843
- match_percentage = analysis.get("match_percentage", 0)
844
- st.markdown(f"### Match Score: {match_percentage}%")
845
- st.markdown('<div class="match-meter"><div class="match-fill" style="width: {}%;"></div></div>'.format(match_percentage), unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
846
 
847
- # Missing skills
848
- if "missing_skills" in analysis and analysis["missing_skills"]:
849
- st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
850
- st.markdown("#### 🔴 Missing Skills")
851
- st.markdown("Consider adding these skills to your resume:")
852
- missing_skills_html = ""
853
- for skill in analysis["missing_skills"]:
854
- missing_skills_html += f'<span class="recommended-skill">{skill} <button onclick="document.getElementById(\'add_skill_{skill.replace(" ", "_")}\').click()">+</button></span>'
855
- st.markdown(missing_skills_html, unsafe_allow_html=True)
856
 
857
- # Hidden buttons for adding missing skills
858
- for skill in analysis["missing_skills"]:
859
- if st.button("Add", key=f"add_skill_{skill.replace(' ', '_')}", type="primary"):
860
- if skill not in data["skills"]:
861
- data["skills"].append(skill)
 
 
 
 
 
 
862
  st.rerun()
863
- st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
864
 
865
- # Highlight skills
866
- if "highlight_skills" in analysis and analysis["highlight_skills"]:
867
- st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
868
- st.markdown("#### 🟢 Relevant Skills")
869
- st.markdown("Highlight these skills prominently in your resume:")
870
- highlight_skills_html = ""
871
- for skill in analysis["highlight_skills"]:
872
- highlight_skills_html += f'<span class="skill-tag">{skill}</span>'
873
- st.markdown(highlight_skills_html, unsafe_allow_html=True)
874
- st.markdown('</div>', unsafe_allow_html=True)
875
 
876
- # Emphasis suggestions
877
- if "emphasis_suggestions" in analysis and analysis["emphasis_suggestions"]:
878
- st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
879
- st.markdown("#### 💡 Experience to Emphasize")
880
- emphasis_html = "<ul>"
881
- for suggestion in analysis["emphasis_suggestions"]:
882
- emphasis_html += f"<li>{suggestion}</li>"
883
- emphasis_html += "</ul>"
884
- st.markdown(emphasis_html, unsafe_allow_html=True)
885
- st.markdown('</div>', unsafe_allow_html=True)
886
 
887
- # Improvement tips
888
- if "improvement_tips" in analysis and analysis["improvement_tips"]:
889
- st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
890
- st.markdown("#### ⚡ Improvement Tips")
891
- tips_html = "<ul>"
892
- for tip in analysis["improvement_tips"]:
893
- tips_html += f"<li>{tip}</li>"
894
- tips_html += "</ul>"
895
- st.markdown(tips_html, unsafe_allow_html=True)
896
- st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
897
 
898
- # Display suggested skills if available
899
- if hasattr(st.session_state, 'suggested_skills') and st.session_state.suggested_skills:
900
- st.markdown("### 🏷️ Suggested Skills for Your Role")
901
- suggested_skills_html = ""
902
- for skill in st.session_state.suggested_skills:
903
- if skill not in data["skills"]:
904
- suggested_skills_html += f'<span class="recommended-skill">{skill} <button onclick="document.getElementById(\'add_suggested_skill_{skill.replace(" ", "_")}\').click()">+</button></span>'
 
 
 
 
 
 
 
905
 
906
- if suggested_skills_html:
907
- st.markdown(suggested_skills_html, unsafe_allow_html=True)
908
-
909
- # Hidden buttons for adding suggested skills
910
- for skill in st.session_state.suggested_skills:
911
- if skill not in data["skills"]:
912
- if st.button("Add", key=f"add_suggested_skill_{skill.replace(' ', '_')}", type="primary"):
913
- data["skills"].append(skill)
914
- st.rerun()
915
- else:
916
- st.info("All suggested skills are already in your resume!")
917
-
918
- # Display cover letter if available
919
- if st.session_state.cover_letter:
920
- st.markdown("### 📝 Generated Cover Letter")
921
- st.markdown('<div class="cover-letter">', unsafe_allow_html=True)
922
- # Replace newlines with <br> tags for proper HTML display
923
- formatted_letter = st.session_state.cover_letter.replace('\n', '<br>')
924
- st.markdown(formatted_letter, unsafe_allow_html=True)
925
- st.markdown('</div>', unsafe_allow_html=True)
926
 
927
- # Add button to copy cover letter to clipboard
928
- if st.button("📋 Copy Cover Letter"):
929
- st.toast("Cover letter copied to clipboard!", icon="📋")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
930
 
931
- st.markdown('</div>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  from io import BytesIO
4
  import json
5
  from datetime import datetime
6
+ import re # Added for regex pattern matching in AI responses
 
 
7
 
8
+ # Set page configuration
9
  st.set_page_config(
10
  page_title="ResumeBuilder Pro",
11
  layout="wide",
12
  initial_sidebar_state="collapsed"
13
  )
14
 
15
+ # Initialize session state variables
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  if "resume_data" not in st.session_state:
17
  st.session_state.resume_data = {
18
  "fullName": "Alexander Johnson",
 
69
  if "suggested_skills" not in st.session_state:
70
  st.session_state.suggested_skills = []
71
 
72
+ # Define a flag to check if API functionality is available
73
+ if "gemini_available" not in st.session_state:
74
+ st.session_state.gemini_available = False
75
+
76
+ # Try to import Google Generative AI library, but make it optional
77
+ try:
78
+ import google.generativeai as genai
79
+ st.session_state.gemini_import_success = True
80
+ except ImportError:
81
+ st.session_state.gemini_import_success = False
82
+
83
  # Apply custom styling based on dark/light mode
84
  def apply_custom_styling():
85
  dark_mode = st.session_state.dark_mode
86
  primary_color = "#4F46E5" # Indigo
87
 
88
  if dark_mode:
89
+ background_color = "#111827"
90
+ text_color = "#F9FAFB"
91
+ card_bg = "#1F2937"
92
+ input_bg = "#374151"
93
+ secondary_bg = "#1E3A8A"
94
+ accent_light = "#818CF8"
95
  else:
96
+ background_color = "#F9FAFB"
97
+ text_color = "#111827"
98
+ card_bg = "#FFFFFF"
99
+ input_bg = "#F3F4F6"
100
+ secondary_bg = "#EEF2FF"
101
+ accent_light = "#C7D2FE"
102
+
103
  css = f"""
104
  <style>
105
  /* Base colors and fonts */
 
111
  --input-bg: {input_bg};
112
  --secondary-bg: {secondary_bg};
113
  --accent-light: {accent_light};
114
+ --spacing-xs: 0.5rem;
115
+ --spacing-sm: 0.75rem;
116
+ --spacing-base: 1rem;
117
+ --spacing-lg: 1.5rem;
118
+ --spacing-xl: 2rem;
119
+ }}
120
+
121
+ /* Reset spacing */
122
+ .stApp [data-testid="stVerticalBlock"] > div:not(:last-child) {{
123
+ margin-bottom: var(--spacing-lg);
124
+ }}
125
+
126
+ /* Fix grid layout */
127
+ .stApp [data-testid="stHorizontalBlock"] {{
128
+ gap: var(--spacing-base);
129
+ align-items: flex-start;
130
+ }}
131
+
132
+ .stApp [data-testid="stHorizontalBlock"] > div {{
133
+ min-width: 0;
134
+ flex: 1;
135
+ }}
136
+
137
+ /* Prevent content overflow */
138
+ .element-container {{
139
+ overflow: hidden;
140
+ word-wrap: break-word;
141
  }}
142
 
143
  /* Main page styling */
144
  .main .block-container {{
145
+ padding: var(--spacing-xl) var(--spacing-base);
146
+ max-width: 1400px;
147
+ margin: 0 auto;
148
  }}
149
 
150
  .stApp {{
151
  background-color: var(--bg-color);
152
  color: var(--text-color);
153
+ overflow-x: hidden;
154
+ }}
155
+
156
+ /* Form layout improvements */
157
+ .stForm > div {{
158
+ margin-bottom: var(--spacing-xl);
159
+ }}
160
+
161
+ .stTextInput, .stTextArea {{
162
+ margin-bottom: var(--spacing-base);
163
+ }}
164
+
165
+ /* Button spacing */
166
+ .stButton {{
167
+ margin: var(--spacing-base) 0;
168
  }}
169
 
170
  /* Header styling */
 
174
 
175
  /* Make inputs match theme */
176
  .stTextInput > div > div > input,
177
+ .stTextArea > div > div > textarea,
178
+ .stSelectbox > div > div > select {{
179
  background-color: var(--input-bg);
180
  color: var(--text-color);
181
  border-radius: 0.375rem;
182
+ padding: var(--spacing-sm);
183
+ margin-bottom: var(--spacing-base);
184
+ }}
185
+
186
+ /* Unified focus states */
187
+ input:focus, textarea:focus, select:focus {{
188
+ outline: 2px solid var(--primary-color);
189
+ outline-offset: 2px;
190
  }}
191
 
192
  /* Custom card styling */
193
  .custom-card {{
194
  background-color: var(--card-bg);
195
  border-radius: 0.5rem;
196
+ padding: var(--spacing-lg);
197
+ margin-bottom: var(--spacing-base);
198
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
199
+ position: relative;
200
+ z-index: 1;
201
+ }}
202
+
203
+ /* Section spacing */
204
+ .section {{
205
+ margin-bottom: var(--spacing-xl);
206
+ }}
207
+
208
+ /* Form groups */
209
+ .form-group {{
210
+ margin-bottom: var(--spacing-lg);
211
+ }}
212
+
213
+ /* Input groups */
214
+ .input-group {{
215
+ display: flex;
216
+ gap: var(--spacing-base);
217
+ margin-bottom: var(--spacing-base);
218
+ }}
219
+
220
+ /* Responsive adjustments */
221
+ @media (max-width: 768px) {{
222
+ .stHorizontalBlock {{
223
+ flex-direction: column;
224
+ }}
225
+
226
+ .input-group {{
227
+ gap: var(--spacing-xs);
228
+ }}
229
+
230
+ .resume-container {{
231
+ padding: var(--spacing-lg);
232
+ }}
233
  }}
234
 
235
  /* AI feature styling */
 
252
 
253
  /* Resume preview styling */
254
  .resume-container {{
255
+ background-color: white;
256
+ color: #333;
257
+ padding: 1.5rem;
258
+ max-width: 800px;
259
+ margin: 0 auto;
260
+ font-family: 'Georgia', serif;
261
+ line-height: 1.4;
262
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
263
  }}
264
 
265
+ /* Clean, minimal header */
266
  .resume-header {{
267
+ text-align: center;
268
  margin-bottom: 1.5rem;
269
+ padding-bottom: 1rem;
270
+ border-bottom: 1px solid #eee;
271
  }}
272
 
273
  .resume-name {{
274
+ font-size: 1.8rem;
275
+ font-weight: normal;
276
+ margin-bottom: 0.5rem;
277
+ color: #333;
278
  }}
279
 
280
  .resume-title {{
281
+ font-size: 1.2rem;
282
+ color: #666;
283
+ font-weight: normal;
284
  margin-bottom: 0.5rem;
285
  }}
286
 
287
  .contact-info {{
288
+ font-size: 0.9rem;
289
+ color: #666;
290
+ display: flex;
291
+ justify-content: center;
292
+ gap: 1rem;
293
+ flex-wrap: wrap;
294
  }}
295
 
296
+ /* Clean section styling */
297
  .section-title {{
298
+ font-size: 1.1rem;
299
+ text-transform: uppercase;
300
+ letter-spacing: 1px;
301
+ color: #333;
302
  margin-top: 1.5rem;
303
+ margin-bottom: 0.75rem;
304
+ font-weight: normal;
305
+ border-bottom: 1px solid #eee;
306
+ padding-bottom: 0.25rem;
307
  }}
308
 
309
+ /* Experience items */
310
+ .experience-item {{
311
+ margin-bottom: 1.25rem;
312
  }}
313
 
314
  .job-title {{
315
+ font-weight: bold;
316
+ font-size: 1rem;
317
+ color: #333;
318
+ margin-bottom: 0.25rem;
319
+ }}
320
+
321
+ .company-name {{
322
+ font-weight: normal;
323
+ font-size: 1rem;
324
+ color: #333;
325
  }}
326
 
327
  .duration {{
328
+ font-size: 0.9rem;
329
+ color: #666;
330
+ font-style: italic;
331
  }}
332
 
333
  .job-description {{
334
+ font-size: 0.95rem;
335
+ color: #444;
336
  margin-top: 0.5rem;
337
+ line-height: 1.5;
338
+ }}
339
+
340
+ /* Education items */
341
+ .education-item {{
342
+ margin-bottom: 1rem;
343
  }}
344
 
345
  .institution {{
346
+ font-weight: bold;
347
+ font-size: 1rem;
348
+ color: #333;
349
+ margin-bottom: 0.25rem;
350
  }}
351
 
352
  .degree {{
353
+ font-size: 0.95rem;
354
+ color: #444;
355
+ }}
356
+
357
+ /* Skills */
358
+ .skills-container {{
359
+ display: flex;
360
+ flex-wrap: wrap;
361
+ gap: 0.5rem;
362
+ margin-top: 0.5rem;
363
  }}
364
 
365
  .skill-tag {{
366
+ background-color: #f5f5f5;
367
+ color: #333;
368
+ padding: 0.25rem 0.5rem;
369
+ border-radius: 3px;
370
+ font-size: 0.85rem;
371
+ border: none;
 
 
372
  }}
373
 
374
  .recommended-skill {{
375
  display: inline-block;
376
+ background-color: rgba(5, 150, 105, 0.1);
377
+ color: #059669;
378
  padding: 0.35rem 0.7rem;
379
  border-radius: 9999px;
380
  margin-right: 0.5rem;
381
  margin-bottom: 0.5rem;
382
  font-size: 0.875rem;
383
+ border: 1px dashed rgba(5, 150, 105, 0.5);
384
  }}
385
 
386
  /* Tab styling */
 
404
  border-color: {f"rgba(255,255,255,0.1)" if dark_mode else "rgba(0,0,0,0.1)"};
405
  }}
406
 
407
+ /* Unified transition effects */
408
+ .stButton > button,
409
+ .custom-card {{
410
+ transition: all 0.2s ease-in-out;
411
+ }}
412
+
413
+ /* Enhanced dark mode contrast */
414
+ .stMarkdown {{
415
+ color: var(--text-color) !important;
416
+ }}
417
+
418
  /* Button styling */
419
  .stButton button {{
420
  border-radius: 0.375rem;
421
  }}
422
+
423
+ /* Theme toggle styling */
424
+ .theme-toggle button {{
425
+ background-color: var(--primary-color) !important;
426
+ color: var(--text-color) !important;
427
+ border: 1px solid var(--accent-light);
428
+ border-radius: 20px;
429
+ padding: 8px 16px;
430
+ margin: 0 var(--spacing-sm);
431
+ }}
432
+
433
+ .theme-toggle button:hover {{
434
+ opacity: 0.9;
435
+ transform: scale(1.02);
436
+ }}
437
 
438
  /* Make the buttons in header look nicer */
439
  .header-button {{
 
510
 
511
  /* Cover letter styling */
512
  .cover-letter {{
513
+ background-color: white;
514
+ color: #333;
515
+ padding: 1.5rem;
516
+ font-family: 'Georgia', serif;
517
+ line-height: 1.6;
518
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
519
  margin-top: 1rem;
520
  }}
521
 
 
553
  border: 1px dashed var(--primary-color);
554
  text-align: center;
555
  }}
556
+
557
+ /* Print-friendly resume styles */
558
+ @media print {{
559
+ .resume-container {{
560
+ box-shadow: none;
561
+ padding: 0;
562
+ max-width: 100%;
563
+ }}
564
+
565
+ .section-title {{
566
+ color: #000 !important;
567
+ border-bottom-color: #000 !important;
568
+ }}
569
+
570
+ .resume-name, .resume-title, .job-title, .institution {{
571
+ color: #000 !important;
572
+ }}
573
+
574
+ .company-name {{
575
+ color: #333 !important;
576
+ }}
577
+
578
+ .skill-tag {{
579
+ border: 1px solid #ccc;
580
+ background: #f5f5f5 !important;
581
+ color: #333 !important;
582
+ }}
583
+ }}
584
  </style>
585
  """
586
  st.markdown(css, unsafe_allow_html=True)
 
590
  json_str = json.dumps(st.session_state.resume_data, indent=2)
591
  b64 = base64.b64encode(json_str.encode()).decode()
592
  filename = f"resume_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
593
+ href = f'<a href="data:file/json;base64,{b64}" download="{filename}" class="header-button" style="text-decoration:none;padding:0.5rem 1rem;margin-right:0.5rem;">📄 Download JSON</a>'
594
  return href
595
 
596
  # Function to create a placeholder for PDF export
597
  def download_resume_pdf():
598
  # In a real app, you would generate an actual PDF here
599
+ # For this example, we'll just return a placeholder button
600
+ return '<button class="header-button" onclick="alert(\'PDF generation would be implemented here in a real app\');">📃 Export PDF</button>'
601
+
602
+ # Initialize Gemini API (modified to handle missing API key gracefully)
603
+ def initialize_gemini_api():
604
+ if not st.session_state.gemini_import_success:
605
+ st.warning("Google Generative AI library not installed. Install with: `pip install google-generativeai`")
606
+ return False
607
+
608
+ try:
609
+ # Get API key from session state first (from text input)
610
+ api_key = st.session_state.get("api_key", "")
611
+
612
+ # Only try to configure if API key is provided
613
+ if api_key:
614
+ genai.configure(api_key=api_key)
615
+ # Test the API with a simple call to verify it works
616
+ model = genai.GenerativeModel(model_name="gemini-1.5-pro")
617
+ _ = model.generate_content("Hello")
618
+ return True
619
+ else:
620
+ # No API key provided yet, not an error
621
+ return False
622
+ except Exception as e:
623
+ st.error(f"Failed to initialize Gemini API: {str(e)}")
624
+ return False
625
+
626
+ # Function to get Gemini model response
627
+ def get_gemini_response(prompt, temperature=0.7):
628
+ if not st.session_state.gemini_available:
629
+ st.warning("Gemini API not available. Please set up your API key.")
630
+ return None
631
+
632
+ try:
633
+ # Create the model
634
+ generation_config = {
635
+ "temperature": temperature,
636
+ "top_p": 0.95,
637
+ "top_k": 64,
638
+ "max_output_tokens": 8192,
639
+ }
640
+
641
+ model = genai.GenerativeModel(
642
+ model_name="gemini-1.5-pro",
643
+ generation_config=generation_config,
644
+ )
645
+
646
+ response = model.generate_content(prompt)
647
+ return response.text
648
+ except Exception as e:
649
+ st.error(f"Error getting AI response: {str(e)}")
650
+ return None
651
 
652
  # Main application header
653
  def render_header():
 
655
 
656
  with col1:
657
  ai_badge = '<span style="background-color: var(--primary-color); color: white; font-size: 0.6em; padding: 0.2em 0.5em; border-radius: 0.5em; margin-left: 0.5em;">AI-Ready</span>' if st.session_state.gemini_import_success else ""
658
+ st.markdown(f'<h1 style="display:flex;align-items:center;"><span style="margin-right:10px;">📄</span> ResumeBuilder Pro {ai_badge}</h1>', unsafe_allow_html=True)
659
 
660
  with col2:
661
  download_buttons = f"""
 
693
 
694
  # Show different message if the generative AI library is not installed
695
  if not st.session_state.gemini_import_success:
696
+ st.markdown("### 🤖 Enable AI Features")
697
  st.markdown("""
698
  To use AI features, you need to install the Google Generative AI library:
699
  ```
 
703
  """)
704
  st.markdown('</div>', unsafe_allow_html=True)
705
  return
706
+
707
  # If library is installed but API key not set
708
  st.markdown("### 🔑 Gemini API Setup")
709
  st.markdown("To use AI features, enter your Google Gemini API key below:")
710
+
711
  api_key = st.text_input("API Key",
712
  value=st.session_state.api_key,
713
  type="password",
714
+ placeholder="Enter your Gemini API key here",
715
+ key="api_key_input")
716
+
717
  col1, col2 = st.columns([1, 3])
718
  with col1:
719
+ if st.button("Save API Key", key="save_api_key_button"):
720
  st.session_state.api_key = api_key
721
  if initialize_gemini_api():
722
  st.session_state.gemini_available = True
 
726
  st.error("Invalid API key. Please check and try again.")
727
  else:
728
  st.warning("Please enter an API key to enable AI features.")
729
+
730
  with col2:
731
  st.markdown("Get your API key from [Google AI Studio](https://makersuite.google.com/app/apikey)")
732
+
733
  # Show what AI features are available
734
  if not st.session_state.gemini_available:
735
  st.markdown('<div class="ai-features-banner">', unsafe_allow_html=True)
736
+ st.markdown("### Unlock AI Features")
737
  st.markdown("""
738
  By adding your API key, you'll unlock powerful AI features:
739
 
 
747
 
748
  st.markdown('</div>', unsafe_allow_html=True)
749
 
750
+ # Generate AI summary function
 
 
751
  def generate_ai_summary():
752
  data = st.session_state.resume_data
 
 
753
  prompt = f"""
754
  You are an expert resume writer. Generate a compelling professional summary for a resume with these details:
755
+
756
  Name: {data['fullName']}
757
  Current Position: {data['title']}
758
  Skills: {', '.join(data['skills'])}
759
+
760
  Experience:
761
  {' '.join([f"{exp['position']} at {exp['company']} ({exp['duration']}): {exp['description']}" for exp in data['experience']])}
762
+
763
  Education:
764
  {' '.join([f"{edu['degree']} from {edu['institution']} ({edu['duration']})" for edu in data['education']])}
765
+
766
  Rules for writing the summary:
767
  1. Keep it concise (3-4 sentences maximum)
768
  2. Highlight key skills and accomplishments
769
  3. Focus on value provided in past roles
770
  4. Use active language and avoid clichés
771
  5. Target it toward professional growth
772
+
773
  Write ONLY the summary. Don't include explanations or other text.
774
  """
 
775
  with st.spinner("Generating professional summary..."):
776
  summary = get_gemini_response(prompt, temperature=0.7)
777
  if summary:
778
  # Clean up the response
779
  summary = summary.strip().replace('"', '')
780
+
781
  return summary
782
+ return None
783
 
784
+ # Job description analysis function
785
  def analyze_job_description(job_description):
786
  data = st.session_state.resume_data
787
+
788
  prompt = f"""
789
  You are an expert resume consultant. Analyze this job description and the candidate's resume to provide insights.
790
+
791
  JOB DESCRIPTION:
792
  {job_description}
793
+
794
  CANDIDATE RESUME:
795
  Name: {data['fullName']}
796
  Current Position: {data['title']}
797
  Skills: {', '.join(data['skills'])}
798
+
799
  Experience:
800
  {' '.join([f"{exp['position']} at {exp['company']} ({exp['duration']}): {exp['description']}" for exp in data['experience']])}
801
+
802
  Education:
803
  {' '.join([f"{edu['degree']} from {edu['institution']} ({edu['duration']})" for edu in data['education']])}
804
+
805
  Provide the following in JSON format:
806
  1. "match_percentage": A numerical estimate (0-100) of how well the candidate's skills match the job requirements
807
  2. "missing_skills": A list of 3-5 key skills mentioned in the job that are missing from the candidate's resume
808
  3. "highlight_skills": A list of skills the candidate has that are particularly relevant to this job
809
  4. "emphasis_suggestions": 2-3 specific parts of the candidate's experience that should be emphasized for this job
810
  5. "improvement_tips": 2-3 brief suggestions to improve the resume for this specific job
811
+
812
  Return ONLY the JSON, formatted as follows:
813
  {{
814
  "match_percentage": number,
 
818
  "improvement_tips": [list of strings]
819
  }}
820
  """
821
+
822
  with st.spinner("Analyzing job description..."):
823
  analysis = get_gemini_response(prompt, temperature=0.2)
824
  if analysis:
825
  try:
826
+ # Try to parse the entire response first
827
+ try:
828
+ return json.loads(analysis)
829
+ except json.JSONDecodeError:
830
+ # If direct parsing fails, try to extract JSON
831
+ json_match = re.search(r'\{[\s\S]*\}', analysis)
832
+ if json_match:
833
+ json_str = json_match.group(0)
834
+ return json.loads(json_str)
835
+
836
+ # If still no valid JSON found, return None
837
+ return None
838
+
839
  except Exception as e:
840
  st.error(f"Error parsing AI response: {str(e)}")
841
  st.write("Raw response:", analysis)
842
+ return None
843
+ return None
844
 
845
+ # Generate cover letter function
846
+ def generate_cover_letter(job_description):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
847
  data = st.session_state.resume_data
848
+
849
  prompt = f"""
850
+ You are an expert cover letter writer. Create a professional cover letter based on this person's resume and the job description.
851
+
852
+ JOB DESCRIPTION:
853
+ {job_description}
854
 
855
+ CANDIDATE RESUME:
856
+ Name: {data['fullName']}
857
  Current Position: {data['title']}
858
  Skills: {', '.join(data['skills'])}
 
859
 
860
+ Experience:
861
+ {' '.join([f"{exp['position']} at {exp['company']} ({exp['duration']}): {exp['description']}" for exp in data['experience']])}
862
 
863
+ Education:
864
+ {' '.join([f"{edu['degree']} from {edu['institution']} ({edu['duration']})" for edu in data['education']])}
865
 
866
+ Rules for the cover letter:
867
+ 1. Keep it concise (3-4 paragraphs)
868
+ 2. Personalize it to the job description
869
+ 3. Highlight relevant experience and skills
870
+ 4. Use professional language but maintain a conversational tone
871
+ 5. Include a strong opening and closing
872
+ 6. Don't include the date or contact information
 
873
 
874
+ Write ONLY the cover letter content.
 
875
  """
876
 
877
  with st.spinner("Generating cover letter..."):
878
  cover_letter = get_gemini_response(prompt, temperature=0.7)
879
  if cover_letter:
880
  return cover_letter.strip()
881
+ return None
882
 
883
+ # Main application layout
884
+ def main():
885
+ # Apply custom styling
886
+ apply_custom_styling()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
887
 
888
+ # Create a container for the entire app
889
+ with st.container():
890
+ # Render header
891
+ render_header()
892
+
893
+ # Create tabs
894
+ tabs = st.tabs(["📝 Resume Editor", "🎯 Job Match", "✉️ Cover Letter"])
895
 
896
+ # Edit Resume Tab
897
+ with tabs[0]:
898
+ if st.session_state.show_preview:
899
+ col1, col2 = st.columns([3, 4])
900
+ else:
901
+ col1 = st.container()
902
+
903
+ # Edit form in left column
904
+ with col1:
905
+ st.markdown('<div class="custom-card">', unsafe_allow_html=True)
906
+ st.subheader("Personal Information")
907
 
908
+ # Personal info fields
909
+ st.session_state.resume_data["fullName"] = st.text_input("Full Name", value=st.session_state.resume_data["fullName"], key="fullname_input")
910
+ st.session_state.resume_data["title"] = st.text_input("Professional Title", value=st.session_state.resume_data["title"], key="title_input")
911
 
912
+ col1_1, col1_2 = st.columns(2)
913
+ with col1_1:
914
+ st.session_state.resume_data["email"] = st.text_input("Email", value=st.session_state.resume_data["email"], key="email_input")
915
+ with col1_2:
916
+ st.session_state.resume_data["phone"] = st.text_input("Phone", value=st.session_state.resume_data["phone"], key="phone_input")
 
 
 
917
 
918
+ st.session_state.resume_data["location"] = st.text_input("Location", value=st.session_state.resume_data["location"], key="location_input")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
919
 
920
+ # Summary with AI assistance
921
+ st.markdown("### Professional Summary")
 
 
 
 
 
 
922
 
923
+ # Show AI button if API is available
924
+ if st.session_state.gemini_available:
925
+ sum_col1, sum_col2 = st.columns([5, 1])
926
+ with sum_col1:
927
+ st.session_state.resume_data["summary"] = st.text_area(
928
+ "Summary (Professional Overview)",
929
+ value=st.session_state.resume_data["summary"],
930
+ height=150,
931
+ key="summary_input"
932
+ )
933
+ with sum_col2:
934
+ if st.button("✨ AI Generate", key="generate_summary"):
935
+ summary = generate_ai_summary()
936
+ if summary:
937
+ st.session_state.resume_data["summary"] = summary
938
+ st.rerun()
939
+ else:
940
+ st.session_state.resume_data["summary"] = st.text_area(
941
+ "Summary (Professional Overview)",
942
+ value=st.session_state.resume_data["summary"],
943
+ height=150,
944
+ key="summary_input_no_ai"
945
+ )
946
+
947
+ st.markdown('</div>', unsafe_allow_html=True) # End of personal info card
948
+
949
+ # Experience section
950
+ st.markdown('<div class="custom-card">', unsafe_allow_html=True)
951
+ st.subheader("Experience")
952
 
953
+ # Create fields for each experience item
954
+ for i, exp in enumerate(st.session_state.resume_data["experience"]):
955
+ col_exp1, col_exp2, col_exp3 = st.columns([4, 4, 2])
956
+
957
+ with col_exp1:
958
+ st.session_state.resume_data["experience"][i]["position"] = st.text_input(
959
+ "Position/Title",
960
+ value=exp["position"],
961
+ key=f"position_{i}"
962
+ )
963
+
964
+ with col_exp2:
965
+ st.session_state.resume_data["experience"][i]["company"] = st.text_input(
966
+ "Company",
967
+ value=exp["company"],
968
+ key=f"company_{i}"
969
+ )
970
+
971
+ with col_exp3:
972
+ st.session_state.resume_data["experience"][i]["duration"] = st.text_input(
973
+ "Duration",
974
+ value=exp["duration"],
975
+ key=f"duration_{i}"
976
+ )
977
+
978
+ # Description with AI enhancement option
979
+ if st.session_state.gemini_available:
980
+ desc_col1, desc_col2 = st.columns([5, 1])
981
+ with desc_col1:
982
+ st.session_state.resume_data["experience"][i]["description"] = st.text_area(
983
+ "Description",
984
+ value=exp["description"],
985
+ height=100,
986
+ key=f"description_{i}"
987
+ )
988
+ with desc_col2:
989
+ if st.button("✨ Enhance", key=f"enhance_desc_{i}"):
990
+ prompt = f"""
991
+ You are an expert resume writer. Improve this job description to make it more impactful and professional:
992
+
993
+ "{exp["description"]}"
994
+
995
+ Guidelines:
996
+ 1. Use strong action verbs
997
+ 2. Quantify achievements where possible
998
+ 3. Highlight skills and technologies used
999
+ 4. Keep similar length but make it more impactful
1000
+ 5. Focus on results and contributions
1001
+
1002
+ Return ONLY the improved description without quotes or explanations.
1003
+ """
1004
+ enhanced_desc = get_gemini_response(prompt, temperature=0.7)
1005
+ if enhanced_desc:
1006
+ st.session_state.resume_data["experience"][i]["description"] = enhanced_desc.strip()
1007
+ st.rerun()
1008
+ else:
1009
+ st.session_state.resume_data["experience"][i]["description"] = st.text_area(
1010
+ "Description",
1011
+ value=exp["description"],
1012
+ height=100,
1013
+ key=f"description_no_ai_{i}"
1014
+ )
1015
+
1016
+ # Add buttons to remove this experience
1017
+ if st.button("🗑️ Remove this experience", key=f"remove_exp_{i}"):
1018
+ if i < len(st.session_state.resume_data["experience"]):
1019
+ st.session_state.resume_data["experience"].pop(i)
1020
  st.rerun()
1021
+
1022
+ st.markdown("<hr>", unsafe_allow_html=True)
 
 
1023
 
1024
+ # Add new experience button
1025
+ if st.button("➕ Add New Experience", key="add_new_experience"):
1026
+ # Find the highest ID and increment by 1
1027
+ max_id = 0
1028
+ for exp in st.session_state.resume_data["experience"]:
1029
+ if exp["id"] > max_id:
1030
+ max_id = exp["id"]
1031
+
1032
+ st.session_state.resume_data["experience"].append({
1033
+ "id": max_id + 1,
1034
+ "company": "New Company",
1035
+ "position": "Position Title",
1036
+ "duration": "Start - End Date",
1037
+ "description": "Describe your responsibilities and achievements."
1038
+ })
1039
+ st.rerun()
1040
+
1041
+ st.markdown('</div>', unsafe_allow_html=True) # End of experience card
1042
 
1043
+ # Education section
1044
+ with st.container():
1045
+ st.markdown('<div class="custom-card">', unsafe_allow_html=True)
1046
+ st.subheader("Education")
1047
+
1048
+ # Create fields for each education item
1049
+ for i, edu in enumerate(st.session_state.resume_data["education"]):
1050
+ with st.container():
1051
+ col_edu1, col_edu2, col_edu3 = st.columns([4, 4, 2], gap="small")
1052
+
1053
+ with col_edu1:
1054
+ st.session_state.resume_data["education"][i]["institution"] = st.text_input(
1055
+ "Institution",
1056
+ value=edu["institution"],
1057
+ key=f"edit_institution_{i}"
1058
+ )
1059
+
1060
+ with col_edu2:
1061
+ st.session_state.resume_data["education"][i]["degree"] = st.text_input(
1062
+ "Degree",
1063
+ value=edu["degree"],
1064
+ key=f"edit_degree_{i}"
1065
+ )
1066
+
1067
+ with col_edu3:
1068
+ st.session_state.resume_data["education"][i]["duration"] = st.text_input(
1069
+ "Duration",
1070
+ value=edu["duration"],
1071
+ key=f"edit_education_duration_{i}"
1072
+ )
1073
+
1074
+ # Add button to remove this education
1075
+ if st.button("🗑️ Remove this education", key=f"remove_edu_{i}"):
1076
+ st.session_state.resume_data["education"].pop(i)
1077
+ st.rerun()
1078
+
1079
+ st.markdown("<hr>", unsafe_allow_html=True)
1080
+
1081
+ # Add new education button
1082
+ if st.button("➕ Add New Education", key="add_new_education"):
1083
+ # Find the highest ID and increment by 1
1084
+ max_id = 0
1085
+ for edu in st.session_state.resume_data["education"]:
1086
+ if edu["id"] > max_id:
1087
+ max_id = edu["id"]
1088
+
1089
+ st.session_state.resume_data["education"].append({
1090
+ "id": max_id + 1,
1091
+ "institution": "University/College Name",
1092
+ "degree": "Degree Name",
1093
+ "duration": "Start - End Date"
1094
+ })
1095
+ st.rerun()
1096
+
1097
+ st.markdown('</div>', unsafe_allow_html=True) # End of education card
1098
 
1099
+ # Skills section
1100
+ with st.container():
1101
+ st.markdown('<div class="custom-card">', unsafe_allow_html=True)
1102
+ st.subheader("Skills")
 
 
 
 
 
1103
 
1104
+ # Display current skills as tags with remove option
1105
+ with st.container():
1106
+ st.markdown('<div class="skills-container" style="margin-bottom: 1.5rem; display: flex; flex-wrap: wrap; gap: 0.5rem;">', unsafe_allow_html=True)
1107
+ for i, skill in enumerate(st.session_state.resume_data["skills"]):
1108
+ st.markdown(
1109
+ f'<span class="skill-tag" style="margin: 0;">{skill} <button style="background: none; border: none; color: inherit; cursor: pointer; padding: 0 0 0 0.25rem;" onclick="document.getElementById(\'remove_skill_{i}\').click();">✕</button></span>',
1110
+ unsafe_allow_html=True
1111
+ )
1112
+ # Hidden button that will be triggered by the ✕ in the skill tag
1113
+ if st.button("✕", key=f"remove_skill_{i}", type="secondary"):
1114
+ st.session_state.resume_data["skills"].remove(skill)
1115
  st.rerun()
1116
+ st.markdown('</div>', unsafe_allow_html=True)
1117
+
1118
+ # Add new skill
1119
+ col_skill1, col_skill2 = st.columns([4, 1])
1120
+
1121
+ with col_skill1:
1122
+ new_skill = st.text_input("Add Skill", value=st.session_state.new_skill, key="new_skill_input")
1123
+ st.session_state.new_skill = new_skill
1124
+
1125
+ with col_skill2:
1126
+ if st.button("Add", key="add_skill_button") and st.session_state.new_skill.strip():
1127
+ if st.session_state.new_skill not in st.session_state.resume_data["skills"]:
1128
+ st.session_state.resume_data["skills"].append(st.session_state.new_skill)
1129
+ st.session_state.new_skill = ""
1130
+ st.rerun()
1131
+
1132
+ # AI skill suggestions if API is available
1133
+ if st.session_state.gemini_available:
1134
+ if st.button("✨ Suggest relevant skills", key="suggest_skills_section1"):
1135
+ with st.spinner("Generating skill suggestions..."):
1136
+ # Create prompt using current resume data
1137
+ prompt = f"""
1138
+ You are a career advisor. Based on this person's profile, suggest 5-7 relevant technical skills they might want to add to their resume.
1139
+
1140
+ Current professional title: {st.session_state.resume_data["title"]}
1141
+ Current skills: {', '.join(st.session_state.resume_data["skills"])}
1142
+ Experience:
1143
+ {' '.join([f"{exp['position']} at {exp['company']}: {exp['description']}" for exp in st.session_state.resume_data["experience"]])}
1144
+
1145
+ Return ONLY a Python list of strings with the skill names, like this:
1146
+ ["Skill 1", "Skill 2", "Skill 3", "Skill 4", "Skill 5"]
1147
+
1148
+ Suggest only skills that are NOT already listed. Focus on technical and professional skills relevant to their field.
1149
+ """
1150
+
1151
+ skills_response = get_gemini_response(prompt, temperature=0.2)
1152
+ if skills_response:
1153
+ try:
1154
+ # Improved parsing of AI response - more robust handling
1155
+ # First try to find a list pattern in the response
1156
+ list_pattern = re.search(r'\[.*?\]', skills_response, re.DOTALL)
1157
+ if list_pattern:
1158
+ list_str = list_pattern.group(0)
1159
+ # Try to safely parse the list
1160
+ try:
1161
+ suggested_skills = json.loads(list_str)
1162
+ if isinstance(suggested_skills, list):
1163
+ # Filter out skills they already have
1164
+ st.session_state.suggested_skills = [s for s in suggested_skills if s not in st.session_state.resume_data["skills"]]
1165
+ else:
1166
+ st.error("AI response was not in the expected format. Please try again.")
1167
+ except json.JSONDecodeError:
1168
+ # If JSON parsing fails, try a more lenient approach
1169
+ # Extract items that look like skills from the response
1170
+ skill_matches = re.findall(r'"([^"]+)"', list_str)
1171
+ if skill_matches:
1172
+ st.session_state.suggested_skills = [s for s in skill_matches if s not in st.session_state.resume_data["skills"]]
1173
+ else:
1174
+ st.error("Could not parse skills from AI response. Please try again.")
1175
+ else:
1176
+ # If no list pattern found, look for quoted strings that might be skills
1177
+ skill_matches = re.findall(r'"([^"]+)"', skills_response)
1178
+ if skill_matches:
1179
+ st.session_state.suggested_skills = [s for s in skill_matches if s not in st.session_state.resume_data["skills"]]
1180
+ else:
1181
+ st.error("Could not identify skills in AI response. Please try again.")
1182
+ except Exception as e:
1183
+ st.error(f"Error processing skill suggestions: {str(e)}")
1184
+
1185
+ # Display suggested skills if any
1186
+ if st.session_state.suggested_skills:
1187
+ st.markdown('<div style="margin-top: 1rem;">', unsafe_allow_html=True)
1188
+ st.markdown("##### Suggested Skills:")
1189
+ for i, skill in enumerate(st.session_state.suggested_skills):
1190
+ st.markdown(
1191
+ f'<span class="recommended-skill">{skill} <button style="background: none; border: none; color: inherit; cursor: pointer; padding: 0 0 0 0.25rem;" onclick="document.getElementById(\'add_suggested_skill_{i}\').click();">+</button></span>',
1192
+ unsafe_allow_html=True
1193
+ )
1194
+ # Hidden button that will be triggered by the + in the skill tag
1195
+ if st.button("+", key=f"add_suggested_skill_{i}", type="secondary"):
1196
+ if skill not in st.session_state.resume_data["skills"]:
1197
+ st.session_state.resume_data["skills"].append(skill)
1198
+ st.session_state.suggested_skills.remove(skill)
1199
+ st.rerun()
1200
+ st.markdown('</div>', unsafe_allow_html=True)
1201
+
1202
+ st.markdown('</div>', unsafe_allow_html=True) # End of skills card
1203
+
1204
+ # Preview in right column (if enabled)
1205
+ if st.session_state.show_preview:
1206
+ if col2 is not None:
1207
+ with col2:
1208
+ st.markdown('<div class="resume-container">', unsafe_allow_html=True)
1209
+
1210
+ # Header section with clean styling
1211
+ st.markdown('<div class="resume-header">', unsafe_allow_html=True)
1212
+ st.markdown(f'<div class="resume-name">{st.session_state.resume_data["fullName"]}</div>', unsafe_allow_html=True)
1213
+ st.markdown(f'<div class="resume-title">{st.session_state.resume_data["title"]}</div>', unsafe_allow_html=True)
1214
+
1215
+ # Contact info with better spacing
1216
+ contact_info = f'<div class="contact-info">'
1217
+ contact_info += f'<span>{st.session_state.resume_data["email"]}</span>'
1218
+ contact_info += f'<span>{st.session_state.resume_data["phone"]}</span>'
1219
+ contact_info += f'<span>{st.session_state.resume_data["location"]}</span>'
1220
+ contact_info += f'</div>'
1221
+ st.markdown(contact_info, unsafe_allow_html=True)
1222
+ st.markdown('</div>', unsafe_allow_html=True)
1223
+
1224
+ # Summary
1225
+ st.markdown('<div class="section-title">SUMMARY</div>', unsafe_allow_html=True)
1226
+ st.markdown(f'<div>{st.session_state.resume_data["summary"]}</div>', unsafe_allow_html=True)
1227
+
1228
+ # Experience
1229
+ st.markdown('<div class="section-title">EXPERIENCE</div>', unsafe_allow_html=True)
1230
+ for exp in st.session_state.resume_data["experience"]:
1231
+ st.markdown(f'<div class="experience-item">', unsafe_allow_html=True)
1232
+ st.markdown(f'<div class="job-title">{exp["position"]}</div>', unsafe_allow_html=True)
1233
+ st.markdown(f'<div><span class="company-name">{exp["company"]}</span> <span class="duration">| {exp["duration"]}</span></div>', unsafe_allow_html=True)
1234
+ st.markdown(f'<div class="job-description">{exp["description"]}</div>', unsafe_allow_html=True)
1235
+ st.markdown('</div>', unsafe_allow_html=True)
1236
+
1237
+ # Education
1238
+ st.markdown('<div class="section-title">EDUCATION</div>', unsafe_allow_html=True)
1239
+ for edu in st.session_state.resume_data["education"]:
1240
+ st.markdown(f'<div class="education-item">', unsafe_allow_html=True)
1241
+ st.markdown(f'<div class="institution">{edu["institution"]}</div>', unsafe_allow_html=True)
1242
+ st.markdown(f'<div><span class="degree">{edu["degree"]}</span> <span class="duration">| {edu["duration"]}</span></div>', unsafe_allow_html=True)
1243
+ st.markdown('</div>', unsafe_allow_html=True)
1244
+
1245
+ # Skills
1246
+ st.markdown('<div class="section-title">SKILLS</div>', unsafe_allow_html=True)
1247
+ st.markdown('<div class="skills-container">', unsafe_allow_html=True)
1248
+ for skill in st.session_state.resume_data["skills"]:
1249
+ st.markdown(f'<span class="skill-tag">{skill}</span>', unsafe_allow_html=True)
1250
+ st.markdown('</div>', unsafe_allow_html=True)
1251
+
1252
+ st.markdown('</div>', unsafe_allow_html=True) # End of resume preview container
1253
+
1254
+ # Print button for resume
1255
+ st.markdown('<div style="text-align: center; margin-top: 1rem;">', unsafe_allow_html=True)
1256
+ st.markdown('<button class="header-button" onclick="window.print()">🖨️ Print Resume</button>', unsafe_allow_html=True)
1257
+ st.markdown('</div>', unsafe_allow_html=True)
1258
+
1259
+ # Import resume section
1260
+ st.markdown('<div class="custom-card" style="margin-top: 1.5rem;">', unsafe_allow_html=True)
1261
+ st.subheader("Import Resume")
1262
+ uploaded_file = st.file_uploader("Import Resume JSON", type=["json"], key="resume_uploader")
1263
+ if uploaded_file is not None:
1264
+ try:
1265
+ imported_data = json.loads(uploaded_file.getvalue().decode())
1266
+ # Validate the imported data has the required fields
1267
+ required_fields = ["fullName", "title", "email", "phone", "location", "summary", "experience", "education", "skills"]
1268
+ if all(field in imported_data for field in required_fields):
1269
+ st.session_state.resume_data = imported_data
1270
+ st.success("Resume data imported successfully!")
1271
+ st.rerun()
1272
+ else:
1273
+ st.error("Invalid resume data format. Please upload a valid JSON file.")
1274
+ except Exception as e:
1275
+ st.error(f"Error importing data: {str(e)}")
1276
+ st.markdown('</div>', unsafe_allow_html=True) # End of import card
1277
+
1278
+ # About section
1279
+ st.markdown('<div class="custom-card" style="margin-top: 1.5rem;">', unsafe_allow_html=True)
1280
+ st.subheader("About ResumeBuilder Pro")
1281
+ st.markdown("""
1282
+ **ResumeBuilder Pro** is a streamlined tool for creating, editing, and optimizing resumes.
1283
+
1284
+ Features:
1285
+ - Create and edit professional resumes with a user-friendly interface
1286
+ - AI-powered suggestions to enhance your resume content (with Gemini API)
1287
+ - Match your resume against job descriptions
1288
+ - Generate customized cover letters
1289
+ - Export your resume in various formats
1290
+
1291
+ Version 1.0.0
1292
+ """)
1293
+ st.markdown('</div>', unsafe_allow_html=True) # End of about card
1294
+
1295
+ # Job Match Tab
1296
+ with tabs[1]:
1297
+ st.markdown('<div class="custom-card">', unsafe_allow_html=True)
1298
+ st.subheader("Resume-Job Matching")
1299
+
1300
+ if not st.session_state.gemini_available:
1301
+ manage_api_key()
1302
+ else:
1303
+ st.markdown("""
1304
+ Analyze how well your resume matches a specific job description.
1305
+ Paste the job description below to get personalized recommendations.
1306
+ """)
1307
 
1308
+ # Job description input
1309
+ st.session_state.job_description = st.text_area(
1310
+ "Paste the job description here",
1311
+ height=200,
1312
+ value=st.session_state.job_description,
1313
+ key="job_description_input"
1314
+ )
 
 
 
1315
 
1316
+ if st.button("🔍 Analyze Match", key="analyze_match_button") and st.session_state.job_description:
1317
+ analysis = analyze_job_description(st.session_state.job_description)
1318
+ if analysis:
1319
+ st.session_state.ai_suggestions = analysis
1320
+ # Extract suggested skills for adding to resume
1321
+ if "missing_skills" in analysis:
1322
+ st.session_state.suggested_skills = analysis["missing_skills"]
 
 
 
1323
 
1324
+ # Display analysis results if available
1325
+ if st.session_state.ai_suggestions and "match_percentage" in st.session_state.ai_suggestions:
1326
+ match_percentage = st.session_state.ai_suggestions["match_percentage"]
1327
+
1328
+ st.markdown(f"### Resume Match: {match_percentage}%")
1329
+
1330
+ # Match percentage meter
1331
+ st.markdown(f'<div class="match-meter"><div class="match-fill" style="width:{match_percentage}%;"></div></div>', unsafe_allow_html=True)
1332
+
1333
+ # Missing skills
1334
+ if "missing_skills" in st.session_state.ai_suggestions and st.session_state.ai_suggestions["missing_skills"]:
1335
+ st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
1336
+ st.markdown("#### Missing Skills")
1337
+ st.markdown("Consider adding these skills to your resume:")
1338
+ for skill in st.session_state.ai_suggestions["missing_skills"]:
1339
+ skill_hash = hash(skill)
1340
+ st.markdown(
1341
+ f'<span class="recommended-skill">{skill} <button style="background: none; border: none; color: inherit; cursor: pointer; padding: 0 0 0 0.25rem;" onclick="document.getElementById(\'add_missing_skill_{skill_hash}\').click();">+</button></span>',
1342
+ unsafe_allow_html=True
1343
+ )
1344
+ if st.button("+", key=f"add_missing_skill_{skill_hash}", type="secondary"):
1345
+ if skill not in st.session_state.resume_data["skills"]:
1346
+ st.session_state.resume_data["skills"].append(skill)
1347
+ st.rerun()
1348
+ st.markdown('</div>', unsafe_allow_html=True)
1349
+
1350
+ # Highlight skills
1351
+ if "highlight_skills" in st.session_state.ai_suggestions and st.session_state.ai_suggestions["highlight_skills"]:
1352
+ st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
1353
+ st.markdown("#### Relevant Skills")
1354
+ st.markdown("These skills from your resume are particularly relevant:")
1355
+ for skill in st.session_state.ai_suggestions["highlight_skills"]:
1356
+ st.markdown(f'<span class="skill-tag">{skill}</span>', unsafe_allow_html=True)
1357
+ st.markdown('</div>', unsafe_allow_html=True)
1358
+
1359
+ # Emphasis suggestions
1360
+ if "emphasis_suggestions" in st.session_state.ai_suggestions and st.session_state.ai_suggestions["emphasis_suggestions"]:
1361
+ st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
1362
+ st.markdown("#### Emphasis Suggestions")
1363
+ st.markdown("Consider emphasizing these aspects of your experience:")
1364
+ for suggestion in st.session_state.ai_suggestions["emphasis_suggestions"]:
1365
+ st.markdown(f"- {suggestion}")
1366
+ st.markdown('</div>', unsafe_allow_html=True)
1367
+
1368
+ # Improvement tips
1369
+ if "improvement_tips" in st.session_state.ai_suggestions and st.session_state.ai_suggestions["improvement_tips"]:
1370
+ st.markdown('<div class="analysis-card">', unsafe_allow_html=True)
1371
+ st.markdown("#### Improvement Tips")
1372
+ for tip in st.session_state.ai_suggestions["improvement_tips"]:
1373
+ st.markdown(f"- {tip}")
1374
+ st.markdown('</div>', unsafe_allow_html=True)
1375
+
1376
+ # Generate optimized summary button
1377
+ if st.button("✨ Generate Optimized Summary", key="generate_optimized_summary"):
1378
+ summary = generate_ai_summary()
1379
+ if summary: # Only update if summary was successfully generated
1380
+ st.session_state.resume_data["summary"] = summary
1381
+ st.success("Professional summary updated to better match the job description!")
1382
+ st.rerun()
1383
 
1384
+ st.markdown('</div>', unsafe_allow_html=True) # End of job match card
1385
+
1386
+ # Cover Letter Tab
1387
+ with tabs[2]:
1388
+ st.markdown('<div class="custom-card">', unsafe_allow_html=True)
1389
+ st.subheader("Cover Letter Generator")
1390
+
1391
+ if not st.session_state.gemini_available:
1392
+ manage_api_key()
1393
+ else:
1394
+ st.markdown("""
1395
+ Generate a customized cover letter based on your resume and a job description.
1396
+ Paste the job description below to create a tailored cover letter.
1397
+ """)
1398
 
1399
+ # Job description input for cover letter
1400
+ job_desc_for_letter = st.text_area(
1401
+ "Paste the job description here",
1402
+ height=200,
1403
+ value=st.session_state.job_description,
1404
+ key="job_description_for_letter"
1405
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
1406
 
1407
+ if st.button("✨ Generate Cover Letter", key="generate_cover_letter") and job_desc_for_letter:
1408
+ cover_letter = generate_cover_letter(job_desc_for_letter)
1409
+ if cover_letter:
1410
+ st.session_state.cover_letter = cover_letter
1411
+
1412
+ # Display and edit cover letter
1413
+ if st.session_state.cover_letter:
1414
+ st.markdown("### Your Cover Letter")
1415
+ st.session_state.cover_letter = st.text_area(
1416
+ "Edit your cover letter as needed",
1417
+ value=st.session_state.cover_letter,
1418
+ height=400,
1419
+ key="cover_letter_editor"
1420
+ )
1421
+
1422
+ # Display formatted cover letter
1423
+ st.markdown('<div class="cover-letter">', unsafe_allow_html=True)
1424
+ st.markdown(f"<p>{st.session_state.cover_letter.replace(chr(10), '<br>')}</p>", unsafe_allow_html=True)
1425
+ st.markdown('</div>', unsafe_allow_html=True)
1426
+
1427
+ # Copy to clipboard button (note: this is a placeholder, as direct clipboard access requires JavaScript)
1428
+ st.markdown(
1429
+ f'<button class="header-button" onclick="alert(\'Copy functionality would be implemented with JavaScript in a real app\');">📋 Copy to Clipboard</button>',
1430
+ unsafe_allow_html=True
1431
+ )
1432
+
1433
+ st.markdown('</div>', unsafe_allow_html=True) # End of cover letter card
1434
+
1435
+ # Entry point of the application
1436
+ if __name__ == "__main__":
1437
+ # Try to import the Gemini library
1438
+ try:
1439
+ import google.generativeai as genai
1440
+ ST_GEMINI_IMPORT_SUCCESS = True
1441
+ except ImportError:
1442
+ ST_GEMINI_IMPORT_SUCCESS = False
1443
+
1444
+ # Initialize session state variables if they don't exist
1445
+ if "initialized" not in st.session_state:
1446
+ # General app state
1447
+ st.session_state.initialized = True
1448
+ st.session_state.dark_mode = True # Default to dark mode
1449
+ st.session_state.show_preview = True # Show preview by default
1450
 
1451
+ # API and AI state
1452
+ st.session_state.gemini_import_success = ST_GEMINI_IMPORT_SUCCESS
1453
+ st.session_state.gemini_available = False
1454
+ st.session_state.api_key = ""
1455
+
1456
+ # Resume data
1457
+ st.session_state.resume_data = {
1458
+ "fullName": "Alexander Johnson",
1459
+ "title": "Senior Frontend Developer",
1460
+ "email": "[email protected]",
1461
+ "phone": "(555) 123-4567",
1462
+ "location": "San Francisco, CA",
1463
+ "summary": "Experienced frontend developer with 6+ years specializing in React and modern JavaScript frameworks. Passionate about creating intuitive user interfaces and optimizing web performance.",
1464
+ "experience": [
1465
+ {
1466
+ "id": 1,
1467
+ "company": "Tech Innovations Inc.",
1468
+ "position": "Senior Frontend Developer",
1469
+ "duration": "2019 - Present",
1470
+ "description": "Lead frontend development for enterprise SaaS platform. Improved performance by 40% through code optimization. Mentored junior developers."
1471
+ },
1472
+ {
1473
+ "id": 2,
1474
+ "company": "WebSolutions Co.",
1475
+ "position": "Frontend Developer",
1476
+ "duration": "2017 - 2019",
1477
+ "description": "Developed responsive web applications using React. Collaborated with design team to implement UI/UX improvements."
1478
+ }
1479
+ ],
1480
+ "education": [
1481
+ {
1482
+ "id": 1,
1483
+ "institution": "University of California, Berkeley",
1484
+ "degree": "B.S. Computer Science",
1485
+ "duration": "2013 - 2017"
1486
+ }
1487
+ ],
1488
+ "skills": ["React", "JavaScript", "TypeScript", "HTML/CSS", "Redux", "Next.js", "Tailwind CSS", "UI/UX Design"]
1489
+ }
1490
+ st.session_state.new_skill = ""
1491
+ st.session_state.suggested_skills = []
1492
+
1493
+ # Job matching
1494
+ st.session_state.ai_suggestions = {}
1495
+ st.session_state.job_description = ""
1496
+
1497
+ # Cover letter
1498
+ st.session_state.cover_letter = ""
1499
+
1500
+ # Run the main application
1501
+ main()