ZainabFatimaa commited on
Commit
8f26897
·
verified ·
1 Parent(s): 47843ae

Update src/app.py

Browse files
Files changed (1) hide show
  1. src/app.py +622 -413
src/app.py CHANGED
@@ -36,21 +36,18 @@ try:
36
  SPACY_AVAILABLE = True
37
  except ImportError:
38
  SPACY_AVAILABLE = False
39
- st.warning("spaCy not installed. Some advanced NLP features will be limited.")
40
 
41
  try:
42
  from fuzzywuzzy import fuzz, process
43
  FUZZYWUZZY_AVAILABLE = True
44
  except ImportError:
45
  FUZZYWUZZY_AVAILABLE = False
46
- st.warning("fuzzywuzzy not installed. Using basic string matching instead.")
47
 
48
  try:
49
  import language_tool_python
50
  GRAMMAR_TOOL_AVAILABLE = True
51
  except ImportError:
52
  GRAMMAR_TOOL_AVAILABLE = False
53
- st.warning("language_tool_python not installed. Grammar checking will be disabled.")
54
 
55
  # ML imports
56
  from sklearn.feature_extraction.text import TfidfVectorizer
@@ -73,6 +70,91 @@ from typing import Dict, List
73
  # Set seed for reproducibility
74
  set_seed(42)
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  class ImprovedNLPProcessor:
77
  def __init__(self):
78
  self.setup_nltk()
@@ -105,7 +187,6 @@ class ImprovedNLPProcessor:
105
  (token.isalpha() or token in resume_keywords)):
106
  filtered_tokens.append(token)
107
 
108
- # Return only the most relevant terms
109
  return ' '.join(filtered_tokens[:max_terms])
110
 
111
  class ImprovedChatMemory:
@@ -121,7 +202,6 @@ class ImprovedChatMemory:
121
  }
122
  st.session_state.improved_chat_history.append(conversation)
123
 
124
- # Keep only last 6 conversations
125
  if len(st.session_state.improved_chat_history) > 6:
126
  st.session_state.improved_chat_history = st.session_state.improved_chat_history[-6:]
127
 
@@ -130,9 +210,8 @@ class ImprovedChatMemory:
130
  if not st.session_state.improved_chat_history:
131
  return ""
132
 
133
- # Only use the last conversation for context
134
  last_conv = st.session_state.improved_chat_history[-1]
135
- last_topic = last_conv['user'][:30] # First 30 chars only
136
  return f"Previously discussed: {last_topic}"
137
 
138
  class ImprovedCPUChatbot:
@@ -145,7 +224,6 @@ class ImprovedCPUChatbot:
145
  self.memory = ImprovedChatMemory()
146
  self.is_loaded = False
147
 
148
- # Predefined responses for common resume questions
149
  self.template_responses = {
150
  'experience': "To improve your experience section: Use bullet points with action verbs, quantify achievements with numbers, focus on results rather than duties, and tailor content to match job requirements.",
151
  'ats': "Make your resume ATS-friendly by: Using standard section headings, including relevant keywords naturally, avoiding images and complex formatting, using common fonts like Arial, and saving as PDF.",
@@ -158,7 +236,7 @@ class ImprovedCPUChatbot:
158
  def load_model(_self):
159
  """Load the model with better configuration"""
160
  try:
161
- with st.spinner("Loading AI model (first time may take 2-3 minutes)..."):
162
  tokenizer = AutoTokenizer.from_pretrained(_self.model_name)
163
  tokenizer.pad_token = tokenizer.eos_token
164
 
@@ -168,20 +246,19 @@ class ImprovedCPUChatbot:
168
  low_cpu_mem_usage=True
169
  )
170
 
171
- # Create pipeline with better parameters
172
  text_generator = pipeline(
173
  "text-generation",
174
  model=model,
175
  tokenizer=tokenizer,
176
- device=-1, # CPU only
177
- max_new_tokens=50, # Reduced for better quality
178
  do_sample=True,
179
  temperature=0.8,
180
  top_p=0.85,
181
  top_k=50,
182
- repetition_penalty=1.2, # Reduce repetition
183
  pad_token_id=tokenizer.eos_token_id,
184
- no_repeat_ngram_size=3 # Prevent 3-gram repetition
185
  )
186
 
187
  return model, tokenizer, text_generator
@@ -196,7 +273,6 @@ class ImprovedCPUChatbot:
196
  if result[0] is not None:
197
  self.model, self.tokenizer, self.pipeline = result
198
  self.is_loaded = True
199
- st.success("AI model loaded successfully!")
200
  return True
201
  else:
202
  return False
@@ -206,7 +282,6 @@ class ImprovedCPUChatbot:
206
  """Check if we can use a template response for common questions"""
207
  user_lower = user_input.lower()
208
 
209
- # Check for common patterns
210
  if any(word in user_lower for word in ['experience', 'work history', 'job history']):
211
  return self.template_responses['experience']
212
  elif any(word in user_lower for word in ['ats', 'applicant tracking', 'ats-friendly']):
@@ -217,7 +292,6 @@ class ImprovedCPUChatbot:
217
  return self.template_responses['keywords']
218
  elif any(word in user_lower for word in ['format', 'formatting', 'layout', 'design']):
219
  return self.template_responses['format']
220
- # Add general improvement patterns
221
  elif any(phrase in user_lower for phrase in ['improve my resume', 'better resume', 'hire me', 'get hired', 'land job']):
222
  return "To improve your resume for HR success: Use a clear, professional format with standard headings. Tailor your content to match job descriptions. Quantify achievements with numbers. Include relevant keywords naturally. Keep it to 1-2 pages. Use bullet points with action verbs. Proofread carefully for errors."
223
  elif any(word in user_lower for word in ['help', 'advice', 'tips', 'suggestions']):
@@ -225,38 +299,16 @@ class ImprovedCPUChatbot:
225
 
226
  return None
227
 
228
- def create_simple_prompt(self, user_input: str, resume_context: str = "") -> str:
229
- """Create a very simple, clear prompt"""
230
- # Try template response first
231
- template_response = self.get_template_response(user_input)
232
- if template_response:
233
- return template_response
234
-
235
- # Extract key terms
236
- key_terms = self.nlp_processor.extract_key_terms(user_input)
237
-
238
- # Create simple prompt
239
- if resume_context:
240
- context_snippet = resume_context[:100].replace('\n', ' ')
241
- prompt = f"Resume help: {context_snippet}\nQuestion: {user_input}\nAdvice:"
242
- else:
243
- prompt = f"Resume question: {user_input}\nHelpful advice:"
244
-
245
- return prompt
246
-
247
  def generate_response(self, user_input: str, resume_context: str = "") -> str:
248
- """Generate response with better quality control and timeout handling"""
249
  if not self.is_loaded:
250
  return "Please initialize the AI model first by clicking 'Initialize AI'."
251
 
252
- # Check for template response first (this should catch most questions)
253
  template_response = self.get_template_response(user_input)
254
  if template_response:
255
  self.memory.add_conversation(user_input, template_response)
256
  return template_response
257
 
258
- # For non-template questions, provide a general helpful response instead of using the model
259
- # This avoids the generation loops and stuck behavior
260
  general_response = self.get_comprehensive_advice(user_input)
261
  self.memory.add_conversation(user_input, general_response)
262
  return general_response
@@ -265,169 +317,18 @@ class ImprovedCPUChatbot:
265
  """Provide comprehensive advice based on user input patterns"""
266
  user_lower = user_input.lower()
267
 
268
- # Comprehensive resume improvement advice
269
  if any(phrase in user_lower for phrase in ['improve', 'better', 'enhance', 'optimize']):
270
  return """To improve your resume effectiveness: 1) Tailor it to each job by matching keywords from the job description. 2) Use quantifiable achievements (increased sales by 25%, managed team of 10). 3) Start bullet points with strong action verbs. 4) Keep it concise - ideally 1-2 pages. 5) Use a clean, professional format with consistent styling. 6) Include relevant technical and soft skills. 7) Proofread carefully for any errors."""
271
 
272
- # HR/hiring focused advice
273
  elif any(phrase in user_lower for phrase in ['hr', 'hire', 'hiring', 'recruiter', 'employer']):
274
  return """To make your resume appealing to HR and hiring managers: 1) Use standard section headings they expect (Experience, Education, Skills). 2) Include relevant keywords to pass ATS screening. 3) Show clear career progression and achievements. 4) Make it easy to scan with bullet points and white space. 5) Demonstrate value you can bring to their organization. 6) Include measurable results and impacts."""
275
 
276
- # Job search and career advice
277
  elif any(phrase in user_lower for phrase in ['job', 'career', 'position', 'role', 'work']):
278
  return """For job search success: 1) Customize your resume for each application. 2) Research the company and role requirements. 3) Highlight relevant experience and skills prominently. 4) Use industry-specific terminology. 5) Show how your background aligns with their needs. 6) Include both technical competencies and soft skills."""
279
 
280
- # General help
281
  else:
282
  return """Key resume best practices: Use a professional format with clear headings. Lead with your strongest qualifications. Include relevant keywords naturally. Quantify achievements with specific numbers. Keep descriptions concise but impactful. Ensure error-free writing and consistent formatting. Focus on what value you bring to employers."""
283
-
284
- def get_general_advice(self, user_input: str) -> str:
285
- """Fallback advice for when model fails"""
286
- user_lower = user_input.lower()
287
- if 'experience' in user_lower:
288
- return "Focus on achievements with numbers, use action verbs, and show results."
289
- elif 'skill' in user_lower:
290
- return "List skills that match the job description and organize them by category."
291
- elif 'ats' in user_lower:
292
- return "Use standard headings, include keywords, and avoid complex formatting."
293
- else:
294
- return "Make sure your resume is clear, relevant to the job, and easy to read."
295
-
296
- def clean_response_thoroughly(self, response: str, user_input: str) -> str:
297
- """Thoroughly clean the generated response"""
298
- if not response or len(response.strip()) < 5:
299
- return self.get_general_advice(user_input)
300
-
301
- # Remove common problematic patterns
302
- response = re.sub(r'\|[^|]*\|', '', response) # Remove pipe-separated content
303
- response = re.sub(r'Advice:\s*', '', response) # Remove "Advice:" repetition
304
- response = re.sub(r'\s+', ' ', response) # Replace multiple spaces
305
- response = re.sub(r'[.]{2,}', '.', response) # Replace multiple periods
306
-
307
- # Split into sentences and filter
308
- sentences = [s.strip() for s in response.split('.') if s.strip()]
309
- good_sentences = []
310
-
311
- seen_content = set()
312
- for sentence in sentences[:2]: # Max 2 sentences
313
- if (len(sentence) > 15 and
314
- sentence.lower() not in seen_content and
315
- not sentence.lower().startswith(('you are', 'i am', 'as a', 'how do')) and
316
- 'advice' not in sentence.lower()):
317
-
318
- good_sentences.append(sentence)
319
- seen_content.add(sentence.lower())
320
-
321
- if good_sentences:
322
- response = '. '.join(good_sentences)
323
- if not response.endswith('.'):
324
- response += '.'
325
- else:
326
- response = self.get_general_advice(user_input)
327
-
328
- return response.strip()
329
 
330
- def create_improved_chat_interface(resume_context: str = ""):
331
- """Create improved chat interface"""
332
-
333
- st.header("🤖 AI Resume Assistant")
334
-
335
- # Initialize chatbot
336
- if 'improved_chatbot' not in st.session_state:
337
- st.session_state.improved_chatbot = ImprovedCPUChatbot()
338
-
339
- chatbot = st.session_state.improved_chatbot
340
-
341
- # Model initialization
342
- col1, col2 = st.columns([3, 1])
343
-
344
- with col1:
345
- st.info("Using DistilGPT2 with improved response quality")
346
-
347
- with col2:
348
- if st.button("Initialize AI", type="primary"):
349
- chatbot.initialize()
350
-
351
- # Chat interface
352
- if chatbot.is_loaded:
353
- st.success("✅ AI Ready")
354
-
355
- # Quick questions
356
- st.subheader("Quick Questions")
357
- col1, col2 = st.columns(2)
358
-
359
- with col1:
360
- if st.button("How to improve experience section?"):
361
- st.session_state.quick_question = "What's wrong with my experience section?"
362
-
363
- with col2:
364
- if st.button("Make resume ATS-friendly?"):
365
- st.session_state.quick_question = "How do I make it more ATS-friendly?"
366
-
367
- col3, col4 = st.columns(2)
368
- with col3:
369
- if st.button("Add better keywords?"):
370
- st.session_state.quick_question = "What keywords should I add?"
371
-
372
- with col4:
373
- if st.button("Improve skills section?"):
374
- st.session_state.quick_question = "How can I improve my skills section?"
375
-
376
- # Chat input
377
- user_question = st.text_input(
378
- "Ask about your resume:",
379
- value=st.session_state.get('quick_question', ''),
380
- placeholder="How can I improve my resume?",
381
- key="improved_chat_input"
382
- )
383
-
384
- # Send button and clear
385
- col1, col2 = st.columns([1, 3])
386
- with col1:
387
- send_clicked = st.button("Send", type="primary")
388
- with col2:
389
- if st.button("Clear Chat"):
390
- st.session_state.improved_chat_history = []
391
- if 'quick_question' in st.session_state:
392
- del st.session_state.quick_question
393
- st.experimental_rerun()
394
-
395
- # Generate response
396
- if send_clicked and user_question.strip():
397
- with st.spinner("Generating advice..."):
398
- response = chatbot.generate_response(user_question, resume_context)
399
- if 'quick_question' in st.session_state:
400
- del st.session_state.quick_question
401
- st.experimental_rerun()
402
-
403
- # Display chat history
404
- if st.session_state.improved_chat_history:
405
- st.subheader("💬 Conversation")
406
-
407
- for conv in reversed(st.session_state.improved_chat_history[-3:]): # Show last 3
408
- st.markdown(f"**You:** {conv['user']}")
409
- st.markdown(f"**AI:** {conv['bot']}")
410
- st.caption(f"Time: {conv['timestamp']}")
411
- st.divider()
412
-
413
- else:
414
- st.warning("Click 'Initialize AI' to start chatting")
415
-
416
- with st.expander("ℹ️ Improved Features"):
417
- st.markdown("""
418
- **Improvements in this version:**
419
-
420
- ✅ **Better response quality** - Reduced repetition and loops
421
- ✅ **Template responses** - Instant answers for common questions
422
- ✅ **Improved prompting** - Cleaner, more focused prompts
423
- ✅ **Response filtering** - Better cleaning of generated text
424
- ✅ **Quick questions** - Pre-defined buttons for common queries
425
-
426
- **Model**: DistilGPT2 with enhanced parameters
427
- **Response time**: 1-3 seconds
428
- **Quality**: Significantly improved over basic version
429
- """)
430
- # Download NLTK data if not already present
431
  @st.cache_resource
432
  def download_nltk_data():
433
  try:
@@ -441,7 +342,6 @@ def download_nltk_data():
441
  nltk.download('wordnet', quiet=True)
442
  nltk.download('punkt_tab', quiet=True)
443
 
444
- # Initialize tools with better error handling
445
  @st.cache_resource
446
  def init_tools():
447
  download_nltk_data()
@@ -500,6 +400,25 @@ def basic_grammar_check(text):
500
 
501
  return [type('MockError', (), {'message': issue}) for issue in issues]
502
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503
  class ResumeAnalyzer:
504
  def __init__(self):
505
  self.nlp, self.grammar_tool = init_tools()
@@ -601,25 +520,6 @@ class ResumeAnalyzer:
601
  except:
602
  return "Error extracting TXT text"
603
 
604
- def preprocess_text(self, text):
605
- """Clean and preprocess text"""
606
- text = re.sub(r'[^a-zA-Z\s]', '', text)
607
- text = text.lower()
608
-
609
- try:
610
- tokens = word_tokenize(text)
611
- except LookupError:
612
- tokens = text.split()
613
-
614
- try:
615
- tokens = [self.lemmatizer.lemmatize(token) for token in tokens
616
- if token not in self.stop_words and len(token) > 2]
617
- except LookupError:
618
- tokens = [token for token in tokens
619
- if token not in self.stop_words and len(token) > 2]
620
-
621
- return tokens
622
-
623
  def extract_sections(self, text):
624
  """Extract different sections from resume"""
625
  sections = {}
@@ -740,64 +640,6 @@ class ResumeAnalyzer:
740
 
741
  return min(score, 100)
742
 
743
- def generate_persona_summary(self, text, sections):
744
- """Generate AI-powered persona summary"""
745
- education = sections.get('education', '')
746
- experience = sections.get('experience', '')
747
- skills = sections.get('skills', '')
748
-
749
- degree_match = re.search(r'(bachelor|master|phd|degree|engineering|science|business)',
750
- education.lower())
751
- experience_years = len(re.findall(r'\b\d{4}\b', experience))
752
-
753
- summary_parts = []
754
-
755
- if degree_match:
756
- degree = degree_match.group(1).title()
757
- summary_parts.append(f"A {degree} graduate")
758
- else:
759
- summary_parts.append("A dedicated professional")
760
-
761
- if experience_years > 0:
762
- summary_parts.append(f"with {experience_years}+ years of experience")
763
-
764
- tech_skills, soft_skills = self.extract_skills(text)
765
- if tech_skills:
766
- main_skills = ', '.join(tech_skills[:3])
767
- summary_parts.append(f"skilled in {main_skills}")
768
-
769
- if 'project' in text.lower():
770
- summary_parts.append("with hands-on project experience")
771
-
772
- summary = ' '.join(summary_parts) + "."
773
-
774
- return summary
775
-
776
- def get_claude_analysis(self, text, sections, job_role, ats_score, match_percentage):
777
- """Get detailed analysis from Claude - called only once"""
778
- context = f"""
779
- Resume Analysis Data:
780
- - Target Role: {job_role}
781
- - ATS Score: {ats_score}/100
782
- - Role Match: {match_percentage:.1f}%
783
- - Word Count: {len(text.split())}
784
- - Sections Found: {list(sections.keys())}
785
- - Resume Text (excerpt): {text[:1000]}
786
- """
787
-
788
- prompt = f"""
789
- Please provide a concise analysis of this resume for a {job_role} position. Include:
790
-
791
- 1. **Strengths**: What are the standout elements?
792
- 2. **Improvement Areas**: Specific areas that need work
793
- 3. **Missing Elements**: Key components that should be added
794
- 4. **Action Items**: 3-5 concrete steps to improve the resume
795
-
796
- Keep the analysis professional, constructive, and under 500 words.
797
- """
798
-
799
- return self.chatbot.generate_response(prompt, context, max_tokens=800)
800
-
801
  def create_pdf_report(self, text, sections, ats_score, match_percentage, selected_role, tech_skills, soft_skills, found_keywords):
802
  """Create a PDF report using ReportLab"""
803
  buffer = io.BytesIO()
@@ -851,16 +693,129 @@ class ResumeAnalyzer:
851
  doc.build(story)
852
  buffer.seek(0)
853
  return buffer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
854
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
855
  def main():
856
  st.set_page_config(
857
- page_title="AI Resume Analyzer with Chatbot",
858
  page_icon="📄",
859
- layout="wide"
 
860
  )
861
 
862
- st.title("🚀 AI-Powered Resume Analyzer with Chatbot Assistant")
863
- st.markdown("Upload your resume and get comprehensive analysis with AI-powered insights!")
 
 
 
 
864
 
865
  # Initialize analyzer
866
  try:
@@ -869,34 +824,68 @@ def main():
869
  st.error(f"Error initializing analyzer: {str(e)}")
870
  return
871
 
872
- # Sidebar for job role selection
873
- st.sidebar.header("Analysis Settings")
874
- job_roles = list(analyzer.job_keywords.keys())
875
- selected_role = st.sidebar.selectbox("Select Target Job Role:", job_roles)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
876
 
877
- # Initialize session state for chat
878
  if "chat_history" not in st.session_state:
879
  st.session_state.chat_history = []
880
  if "resume_context" not in st.session_state:
881
  st.session_state.resume_context = ""
882
  if "analysis_done" not in st.session_state:
883
  st.session_state.analysis_done = False
884
- if "claude_analysis" not in st.session_state:
885
- st.session_state.claude_analysis = ""
886
 
887
- # File upload section
888
- st.header("📁 Upload Your Resume")
 
889
  uploaded_file = st.file_uploader(
890
- "Choose your resume file",
891
  type=['pdf', 'docx', 'txt'],
892
- help="Supported formats: PDF, DOCX, TXT"
893
  )
894
 
895
  if uploaded_file is not None:
 
 
 
 
 
 
 
 
 
 
 
896
  # Extract text based on file type
897
  file_type = uploaded_file.type
898
 
899
- with st.spinner("Extracting text from resume..."):
900
  try:
901
  if file_type == "application/pdf":
902
  text = analyzer.extract_text_from_pdf(uploaded_file)
@@ -909,8 +898,7 @@ def main():
909
  return
910
 
911
  if "Error" not in text and text.strip():
912
- # Process the resume
913
- st.success("✅ Resume uploaded and processed successfully!")
914
 
915
  # Store resume context for chatbot
916
  st.session_state.resume_context = text
@@ -922,214 +910,435 @@ def main():
922
  found_keywords, match_percentage = analyzer.keyword_matching(text, selected_role)
923
  ats_score = analyzer.calculate_ats_score(text, sections)
924
 
925
- # Create tabs for different analyses
926
- tab1, tab2, tab3, tab4, tab5 = st.tabs([
927
- "📊 Overview", "🎯 Skills Analysis", "📝 Section Breakdown",
928
- "🔍 ATS Analysis", "📋 Report & Suggestions"
929
  ])
930
 
931
  with tab1:
932
- st.header("Resume Overview")
 
 
 
 
 
 
933
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
934
  col1, col2 = st.columns(2)
935
 
936
  with col1:
937
- # Basic stats
938
- word_count = len(text.split())
939
- char_count = len(text)
 
 
 
 
 
 
 
 
940
 
941
- st.metric("Word Count", word_count)
942
- st.metric("Character Count", char_count)
943
- st.metric("Sections Found", len([s for s in sections.values() if s]))
 
 
944
 
945
  with col2:
946
- st.write("**Found Keywords:**")
947
  if found_keywords:
948
- for keyword in found_keywords:
949
- st.write(f"✅ {keyword}")
 
 
 
950
  else:
951
- st.write("No role-specific keywords found.")
952
-
953
- # Missing keywords
954
- missing_keywords = [kw for kw in analyzer.job_keywords[selected_role]
955
- if kw not in found_keywords]
956
- if missing_keywords:
957
- st.subheader("Suggested Keywords to Add")
958
- for keyword in missing_keywords[:10]: # Show top 10
959
- st.write(f"➕ {keyword}")
960
 
961
- # Add the missing tab2 (Skills Analysis)
962
  with tab2:
963
- st.header("Skills Analysis")
964
 
965
  col1, col2 = st.columns(2)
966
 
967
  with col1:
968
- st.subheader("Technical Skills")
969
  if tech_skills:
 
 
970
  for skill in tech_skills:
971
- st.write(f"🔧 {skill}")
 
 
 
972
  else:
973
- st.write("No technical skills detected")
974
 
975
  with col2:
976
- st.subheader("Soft Skills")
977
  if soft_skills:
 
 
978
  for skill in soft_skills:
979
- st.write(f"🤝 {skill}")
 
 
 
980
  else:
981
- st.write("No soft skills detected")
982
 
983
- # Role matching
984
- st.subheader(f"Match for {selected_role}")
985
- st.metric("Match Percentage", f"{match_percentage:.1f}%")
986
 
987
- if match_percentage >= 70:
988
- st.success("🎯 Excellent match!")
989
- elif match_percentage >= 50:
990
- st.warning("⚠️ Good match with room for improvement")
991
- else:
992
- st.error("❌ Low match - consider adding more relevant skills")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
993
 
994
  with tab3:
995
- st.header("Section Breakdown")
 
 
 
996
 
 
997
  for section_name, section_content in sections.items():
998
- if section_content:
999
- with st.expander(f"📋 {section_name.title()} Section"):
1000
- st.write(section_content)
1001
- else:
1002
- st.warning(f"⚠️ {section_name.title()} section not found or empty")
 
1003
 
1004
- # Section recommendations
1005
- st.subheader("Section Recommendations")
1006
- missing_sections = [name for name, content in sections.items() if not content]
1007
- if missing_sections:
1008
- st.write("**Missing or Empty Sections:**")
1009
- for section in missing_sections:
1010
- st.write(f" {section.title()}")
1011
- else:
1012
- st.success("✅ All major sections are present!")
 
 
 
 
 
 
 
 
 
 
 
1013
 
1014
  with tab4:
1015
- st.header("ATS Analysis")
1016
 
1017
- col1, col2 = st.columns(2)
 
1018
 
1019
  with col1:
1020
- st.metric("ATS Score", f"{ats_score}/100")
 
1021
 
1022
- # ATS Score interpretation
1023
  if ats_score >= 80:
1024
- st.success("🤖 Excellent ATS compatibility!")
1025
  elif ats_score >= 60:
1026
- st.warning("⚠️ Good ATS compatibility")
1027
  else:
1028
- st.error(" Poor ATS compatibility")
1029
 
1030
  with col2:
1031
- # Grammar check
1032
- st.subheader("Grammar Check")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1033
  grammar_issues = analyzer.grammar_check(text)
1034
 
1035
  if len(grammar_issues) == 0:
1036
- st.success("No grammar issues detected!")
1037
  else:
1038
- st.warning(f"⚠️ {len(grammar_issues)} potential issues found")
1039
- with st.expander("View Grammar Issues"):
1040
- for issue in grammar_issues[:10]: # Show first 10
1041
- st.write(f"• {issue.message}")
 
 
 
 
 
 
1042
 
1043
- # ATS Tips
1044
- st.subheader("ATS Optimization Tips")
1045
- ats_tips = [
1046
- "Use standard section headings (Experience, Education, Skills)",
1047
  "Include relevant keywords naturally throughout your resume",
1048
- "Use bullet points for easy scanning",
1049
- "Avoid images, tables, and complex formatting",
1050
- "Use standard fonts (Arial, Calibri, Times New Roman)",
1051
- "Save as PDF to preserve formatting",
1052
- "Include contact information at the top"
 
1053
  ]
1054
 
1055
- for tip in ats_tips:
1056
- st.write(f"💡 {tip}")
1057
 
1058
  with tab5:
1059
- st.header("Comprehensive Analysis & Suggestions")
1060
 
1061
- # Manual recommendations (removed Claude dependency)
1062
- st.subheader("📝 Recommendations")
1063
  recommendations = []
1064
 
 
1065
  if ats_score < 70:
1066
  recommendations.extend([
1067
- "Add more bullet points to improve readability",
1068
- "Ensure contact information is clearly visible",
1069
- "Use standard section headings"
1070
  ])
1071
 
 
1072
  if match_percentage < 60:
1073
- recommendations.append(f"Include more {selected_role}-specific keywords")
1074
 
 
1075
  if not tech_skills:
1076
- recommendations.append("Add a dedicated technical skills section")
 
 
 
 
 
 
 
 
1077
 
1078
  if not sections.get('projects'):
1079
- recommendations.append("Consider adding a projects section")
 
 
 
 
 
 
 
1080
 
1081
- for i, rec in enumerate(recommendations, 1):
1082
- st.write(f"{i}. {rec}")
1083
 
1084
- # Generate PDF report
1085
- st.subheader("📄 Download Report")
1086
- if st.button("Generate PDF Report"):
1087
- try:
1088
- pdf_buffer = analyzer.create_pdf_report(
1089
- text, sections, ats_score, match_percentage,
1090
- selected_role, tech_skills, soft_skills, found_keywords
1091
- )
1092
-
1093
- st.download_button(
1094
- label="📥 Download PDF Report",
1095
- data=pdf_buffer.getvalue(),
1096
- file_name=f"resume_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf",
1097
- mime="application/pdf"
1098
- )
1099
- except Exception as e:
1100
- st.error(f"Error generating PDF: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1101
 
1102
- # Chat Interface - Properly indented inside the file upload condition
1103
- st.markdown("---")
1104
- st.header("💬 Chat with Resume Assistant")
1105
- create_improved_chat_interface(st.session_state.get('resume_context', ''))
1106
 
1107
  except Exception as e:
1108
  st.error(f"Error during analysis: {str(e)}")
1109
  st.error("Please check your resume format and try again.")
1110
 
1111
  else:
1112
- st.error("Could not extract text from the uploaded file. Please check the file format and try again.")
1113
 
1114
  else:
1115
- # Show instructions when no file is uploaded
1116
- st.info("👆 Please upload your resume to get started!")
1117
 
1118
- # Show sample analysis
1119
- with st.expander("📖 What you'll get:", expanded=True):
 
 
1120
  st.markdown("""
1121
- **Our AI-powered resume analyzer provides:**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1122
 
1123
- - **📊 Comprehensive Scoring**: ATS compatibility and role-specific matching scores
1124
- - **🎯 Skills Analysis**: Technical and soft skills identification
1125
- - **📝 Section Breakdown**: Detailed analysis of each resume section
1126
- - **🔍 ATS Optimization**: Tips to improve applicant tracking system compatibility
1127
- - **🤖 AI Chat Assistant**: Ask questions and get personalized advice
1128
- - **📄 PDF Report**: Downloadable analysis report
1129
 
1130
- **Supported Formats**: PDF, DOCX, TXT
 
1131
 
1132
- **Chat Feature**: AI chat assistance with local models
 
 
 
 
 
 
 
 
 
 
1133
  """)
1134
 
1135
  if __name__ == "__main__":
 
36
  SPACY_AVAILABLE = True
37
  except ImportError:
38
  SPACY_AVAILABLE = False
 
39
 
40
  try:
41
  from fuzzywuzzy import fuzz, process
42
  FUZZYWUZZY_AVAILABLE = True
43
  except ImportError:
44
  FUZZYWUZZY_AVAILABLE = False
 
45
 
46
  try:
47
  import language_tool_python
48
  GRAMMAR_TOOL_AVAILABLE = True
49
  except ImportError:
50
  GRAMMAR_TOOL_AVAILABLE = False
 
51
 
52
  # ML imports
53
  from sklearn.feature_extraction.text import TfidfVectorizer
 
70
  # Set seed for reproducibility
71
  set_seed(42)
72
 
73
+ # Custom CSS for better UI
74
+ def load_custom_css():
75
+ st.markdown("""
76
+ <style>
77
+ .main {
78
+ padding-top: 2rem;
79
+ }
80
+
81
+ .metric-card {
82
+ background-color: #f8f9fa;
83
+ border: 1px solid #dee2e6;
84
+ border-radius: 8px;
85
+ padding: 1rem;
86
+ margin: 0.5rem 0;
87
+ text-align: center;
88
+ }
89
+
90
+ .metric-value {
91
+ font-size: 2rem;
92
+ font-weight: bold;
93
+ color: #28a745;
94
+ }
95
+
96
+ .metric-label {
97
+ font-size: 0.9rem;
98
+ color: #6c757d;
99
+ margin-top: 0.5rem;
100
+ }
101
+
102
+ .warning-box {
103
+ background-color: #fff3cd;
104
+ border-left: 4px solid #ffc107;
105
+ padding: 1rem;
106
+ margin: 1rem 0;
107
+ }
108
+
109
+ .success-box {
110
+ background-color: #d4edda;
111
+ border-left: 4px solid #28a745;
112
+ padding: 1rem;
113
+ margin: 1rem 0;
114
+ }
115
+
116
+ .error-box {
117
+ background-color: #f8d7da;
118
+ border-left: 4px solid #dc3545;
119
+ padding: 1rem;
120
+ margin: 1rem 0;
121
+ }
122
+
123
+ .info-box {
124
+ background-color: #d1ecf1;
125
+ border-left: 4px solid #17a2b8;
126
+ padding: 1rem;
127
+ margin: 1rem 0;
128
+ }
129
+
130
+ .skill-tag {
131
+ display: inline-block;
132
+ background-color: #e9ecef;
133
+ border-radius: 4px;
134
+ padding: 0.25rem 0.5rem;
135
+ margin: 0.25rem;
136
+ font-size: 0.875rem;
137
+ }
138
+
139
+ .section-header {
140
+ border-bottom: 2px solid #dee2e6;
141
+ padding-bottom: 0.5rem;
142
+ margin-bottom: 1rem;
143
+ }
144
+
145
+ .nav-pills {
146
+ background-color: #f8f9fa;
147
+ border-radius: 8px;
148
+ padding: 0.5rem;
149
+ margin-bottom: 1rem;
150
+ }
151
+
152
+ .sidebar .sidebar-content {
153
+ background-color: #f8f9fa;
154
+ }
155
+ </style>
156
+ """, unsafe_allow_html=True)
157
+
158
  class ImprovedNLPProcessor:
159
  def __init__(self):
160
  self.setup_nltk()
 
187
  (token.isalpha() or token in resume_keywords)):
188
  filtered_tokens.append(token)
189
 
 
190
  return ' '.join(filtered_tokens[:max_terms])
191
 
192
  class ImprovedChatMemory:
 
202
  }
203
  st.session_state.improved_chat_history.append(conversation)
204
 
 
205
  if len(st.session_state.improved_chat_history) > 6:
206
  st.session_state.improved_chat_history = st.session_state.improved_chat_history[-6:]
207
 
 
210
  if not st.session_state.improved_chat_history:
211
  return ""
212
 
 
213
  last_conv = st.session_state.improved_chat_history[-1]
214
+ last_topic = last_conv['user'][:30]
215
  return f"Previously discussed: {last_topic}"
216
 
217
  class ImprovedCPUChatbot:
 
224
  self.memory = ImprovedChatMemory()
225
  self.is_loaded = False
226
 
 
227
  self.template_responses = {
228
  'experience': "To improve your experience section: Use bullet points with action verbs, quantify achievements with numbers, focus on results rather than duties, and tailor content to match job requirements.",
229
  'ats': "Make your resume ATS-friendly by: Using standard section headings, including relevant keywords naturally, avoiding images and complex formatting, using common fonts like Arial, and saving as PDF.",
 
236
  def load_model(_self):
237
  """Load the model with better configuration"""
238
  try:
239
+ with st.spinner("Loading AI model..."):
240
  tokenizer = AutoTokenizer.from_pretrained(_self.model_name)
241
  tokenizer.pad_token = tokenizer.eos_token
242
 
 
246
  low_cpu_mem_usage=True
247
  )
248
 
 
249
  text_generator = pipeline(
250
  "text-generation",
251
  model=model,
252
  tokenizer=tokenizer,
253
+ device=-1,
254
+ max_new_tokens=50,
255
  do_sample=True,
256
  temperature=0.8,
257
  top_p=0.85,
258
  top_k=50,
259
+ repetition_penalty=1.2,
260
  pad_token_id=tokenizer.eos_token_id,
261
+ no_repeat_ngram_size=3
262
  )
263
 
264
  return model, tokenizer, text_generator
 
273
  if result[0] is not None:
274
  self.model, self.tokenizer, self.pipeline = result
275
  self.is_loaded = True
 
276
  return True
277
  else:
278
  return False
 
282
  """Check if we can use a template response for common questions"""
283
  user_lower = user_input.lower()
284
 
 
285
  if any(word in user_lower for word in ['experience', 'work history', 'job history']):
286
  return self.template_responses['experience']
287
  elif any(word in user_lower for word in ['ats', 'applicant tracking', 'ats-friendly']):
 
292
  return self.template_responses['keywords']
293
  elif any(word in user_lower for word in ['format', 'formatting', 'layout', 'design']):
294
  return self.template_responses['format']
 
295
  elif any(phrase in user_lower for phrase in ['improve my resume', 'better resume', 'hire me', 'get hired', 'land job']):
296
  return "To improve your resume for HR success: Use a clear, professional format with standard headings. Tailor your content to match job descriptions. Quantify achievements with numbers. Include relevant keywords naturally. Keep it to 1-2 pages. Use bullet points with action verbs. Proofread carefully for errors."
297
  elif any(word in user_lower for word in ['help', 'advice', 'tips', 'suggestions']):
 
299
 
300
  return None
301
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  def generate_response(self, user_input: str, resume_context: str = "") -> str:
303
+ """Generate response with better quality control"""
304
  if not self.is_loaded:
305
  return "Please initialize the AI model first by clicking 'Initialize AI'."
306
 
 
307
  template_response = self.get_template_response(user_input)
308
  if template_response:
309
  self.memory.add_conversation(user_input, template_response)
310
  return template_response
311
 
 
 
312
  general_response = self.get_comprehensive_advice(user_input)
313
  self.memory.add_conversation(user_input, general_response)
314
  return general_response
 
317
  """Provide comprehensive advice based on user input patterns"""
318
  user_lower = user_input.lower()
319
 
 
320
  if any(phrase in user_lower for phrase in ['improve', 'better', 'enhance', 'optimize']):
321
  return """To improve your resume effectiveness: 1) Tailor it to each job by matching keywords from the job description. 2) Use quantifiable achievements (increased sales by 25%, managed team of 10). 3) Start bullet points with strong action verbs. 4) Keep it concise - ideally 1-2 pages. 5) Use a clean, professional format with consistent styling. 6) Include relevant technical and soft skills. 7) Proofread carefully for any errors."""
322
 
 
323
  elif any(phrase in user_lower for phrase in ['hr', 'hire', 'hiring', 'recruiter', 'employer']):
324
  return """To make your resume appealing to HR and hiring managers: 1) Use standard section headings they expect (Experience, Education, Skills). 2) Include relevant keywords to pass ATS screening. 3) Show clear career progression and achievements. 4) Make it easy to scan with bullet points and white space. 5) Demonstrate value you can bring to their organization. 6) Include measurable results and impacts."""
325
 
 
326
  elif any(phrase in user_lower for phrase in ['job', 'career', 'position', 'role', 'work']):
327
  return """For job search success: 1) Customize your resume for each application. 2) Research the company and role requirements. 3) Highlight relevant experience and skills prominently. 4) Use industry-specific terminology. 5) Show how your background aligns with their needs. 6) Include both technical competencies and soft skills."""
328
 
 
329
  else:
330
  return """Key resume best practices: Use a professional format with clear headings. Lead with your strongest qualifications. Include relevant keywords naturally. Quantify achievements with specific numbers. Keep descriptions concise but impactful. Ensure error-free writing and consistent formatting. Focus on what value you bring to employers."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  @st.cache_resource
333
  def download_nltk_data():
334
  try:
 
342
  nltk.download('wordnet', quiet=True)
343
  nltk.download('punkt_tab', quiet=True)
344
 
 
345
  @st.cache_resource
346
  def init_tools():
347
  download_nltk_data()
 
400
 
401
  return [type('MockError', (), {'message': issue}) for issue in issues]
402
 
403
+ def display_metric_card(title, value, description=""):
404
+ """Display a metric in a card format"""
405
+ st.markdown(f"""
406
+ <div class="metric-card">
407
+ <div class="metric-value">{value}</div>
408
+ <div class="metric-label">{title}</div>
409
+ {f"<small>{description}</small>" if description else ""}
410
+ </div>
411
+ """, unsafe_allow_html=True)
412
+
413
+ def display_alert_box(message, alert_type="info"):
414
+ """Display alert box with different types"""
415
+ box_class = f"{alert_type}-box"
416
+ st.markdown(f"""
417
+ <div class="{box_class}">
418
+ {message}
419
+ </div>
420
+ """, unsafe_allow_html=True)
421
+
422
  class ResumeAnalyzer:
423
  def __init__(self):
424
  self.nlp, self.grammar_tool = init_tools()
 
520
  except:
521
  return "Error extracting TXT text"
522
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  def extract_sections(self, text):
524
  """Extract different sections from resume"""
525
  sections = {}
 
640
 
641
  return min(score, 100)
642
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
643
  def create_pdf_report(self, text, sections, ats_score, match_percentage, selected_role, tech_skills, soft_skills, found_keywords):
644
  """Create a PDF report using ReportLab"""
645
  buffer = io.BytesIO()
 
693
  doc.build(story)
694
  buffer.seek(0)
695
  return buffer
696
+
697
+ def create_improved_chat_interface(resume_context: str = ""):
698
+ """Create improved chat interface with better UI"""
699
+
700
+ st.markdown('<h3 class="section-header">AI Resume Assistant</h3>', unsafe_allow_html=True)
701
+
702
+ # Initialize chatbot
703
+ if 'improved_chatbot' not in st.session_state:
704
+ st.session_state.improved_chatbot = ImprovedCPUChatbot()
705
+
706
+ chatbot = st.session_state.improved_chatbot
707
+
708
+ # Model status and initialization
709
+ col1, col2 = st.columns([3, 1])
710
+
711
+ with col1:
712
+ if chatbot.is_loaded:
713
+ display_alert_box("AI Assistant is ready to help with your resume questions", "success")
714
+ else:
715
+ display_alert_box("Click 'Initialize AI' to start the assistant", "info")
716
+
717
+ with col2:
718
+ if st.button("Initialize AI", type="primary", use_container_width=True):
719
+ with st.spinner("Initializing AI model..."):
720
+ if chatbot.initialize():
721
+ st.success("AI model loaded successfully!")
722
+ st.rerun()
723
+ else:
724
+ st.error("Failed to initialize AI model")
725
+
726
+ if chatbot.is_loaded:
727
+ # Quick questions section
728
+ st.markdown('<h4 class="section-header">Quick Questions</h4>', unsafe_allow_html=True)
729
+
730
+ quick_questions = [
731
+ ("Experience Section Help", "How can I improve my experience section?"),
732
+ ("ATS Optimization", "How do I make my resume ATS-friendly?"),
733
+ ("Keywords & Skills", "What keywords should I include?"),
734
+ ("Format & Layout", "How should I format my resume?")
735
+ ]
736
+
737
+ cols = st.columns(2)
738
+ for i, (title, question) in enumerate(quick_questions):
739
+ with cols[i % 2]:
740
+ if st.button(title, use_container_width=True):
741
+ st.session_state.quick_question = question
742
+
743
+ # Chat input section
744
+ st.markdown('<h4 class="section-header">Ask a Question</h4>', unsafe_allow_html=True)
745
+
746
+ user_question = st.text_input(
747
+ "Type your resume question here:",
748
+ value=st.session_state.get('quick_question', ''),
749
+ placeholder="How can I improve my resume for better results?",
750
+ key="improved_chat_input"
751
+ )
752
+
753
+ # Action buttons
754
+ col1, col2, col3 = st.columns([1, 1, 2])
755
+ with col1:
756
+ send_clicked = st.button("Send Question", type="primary", use_container_width=True)
757
+ with col2:
758
+ if st.button("Clear Chat", use_container_width=True):
759
+ st.session_state.improved_chat_history = []
760
+ if 'quick_question' in st.session_state:
761
+ del st.session_state.quick_question
762
+ st.rerun()
763
+
764
+ # Generate response
765
+ if send_clicked and user_question.strip():
766
+ with st.spinner("Generating personalized advice..."):
767
+ response = chatbot.generate_response(user_question, resume_context)
768
+ if 'quick_question' in st.session_state:
769
+ del st.session_state.quick_question
770
+ st.rerun()
771
+
772
+ # Display conversation history
773
+ if st.session_state.improved_chat_history:
774
+ st.markdown('<h4 class="section-header">Conversation History</h4>', unsafe_allow_html=True)
775
+
776
+ for conv in reversed(st.session_state.improved_chat_history[-3:]):
777
+ with st.container():
778
+ st.markdown(f"**Question:** {conv['user']}")
779
+ st.markdown(f"**Answer:** {conv['bot']}")
780
+ st.caption(f"Asked at: {conv['timestamp']}")
781
+ st.divider()
782
+
783
+ else:
784
+ # Information about the AI assistant
785
+ st.markdown('<h4 class="section-header">About the AI Assistant</h4>', unsafe_allow_html=True)
786
 
787
+ with st.expander("Features and Capabilities", expanded=True):
788
+ st.markdown("""
789
+ **The AI Resume Assistant provides:**
790
+
791
+ **Instant Expert Advice** - Get immediate answers to common resume questions
792
+
793
+ **Personalized Recommendations** - Tailored advice based on your specific resume content
794
+
795
+ **Quick Response Options** - Pre-built answers for the most frequently asked questions
796
+
797
+ **Conversation Memory** - The assistant remembers your previous questions in the current session
798
+
799
+ **Model Information:**
800
+ - Uses DistilGPT2 with optimized parameters for resume advice
801
+ - Runs locally on CPU for privacy and speed
802
+ - Enhanced with expert knowledge templates for common scenarios
803
+ """)
804
+
805
  def main():
806
  st.set_page_config(
807
+ page_title="Professional Resume Analyzer",
808
  page_icon="📄",
809
+ layout="wide",
810
+ initial_sidebar_state="expanded"
811
  )
812
 
813
+ # Load custom CSS
814
+ load_custom_css()
815
+
816
+ # Header section
817
+ st.title("Professional Resume Analyzer")
818
+ st.markdown("**Comprehensive resume analysis with AI-powered insights and personalized recommendations**")
819
 
820
  # Initialize analyzer
821
  try:
 
824
  st.error(f"Error initializing analyzer: {str(e)}")
825
  return
826
 
827
+ # Sidebar configuration
828
+ with st.sidebar:
829
+ st.header("Analysis Configuration")
830
+
831
+ job_roles = list(analyzer.job_keywords.keys())
832
+ selected_role = st.selectbox(
833
+ "Target Job Role:",
834
+ job_roles,
835
+ help="Select the job role you're targeting to get relevant keyword analysis"
836
+ )
837
+
838
+ st.divider()
839
+
840
+ st.header("Analysis Features")
841
+ st.markdown("""
842
+ **Comprehensive Analysis:**
843
+ - ATS Compatibility Score
844
+ - Skills Detection & Matching
845
+ - Section Structure Analysis
846
+ - Grammar & Language Check
847
+ - Keyword Optimization
848
+ - PDF Report Generation
849
+
850
+ **AI Assistant:**
851
+ - Personalized Resume Advice
852
+ - Quick Expert Recommendations
853
+ - Interactive Q&A
854
+ """)
855
 
856
+ # Initialize session state
857
  if "chat_history" not in st.session_state:
858
  st.session_state.chat_history = []
859
  if "resume_context" not in st.session_state:
860
  st.session_state.resume_context = ""
861
  if "analysis_done" not in st.session_state:
862
  st.session_state.analysis_done = False
 
 
863
 
864
+ # Main content area
865
+ st.markdown('<h2 class="section-header">Upload Resume</h2>', unsafe_allow_html=True)
866
+
867
  uploaded_file = st.file_uploader(
868
+ "Select your resume file",
869
  type=['pdf', 'docx', 'txt'],
870
+ help="Supported formats: PDF, DOCX, TXT (Maximum size: 200MB)"
871
  )
872
 
873
  if uploaded_file is not None:
874
+ # Show file information
875
+ file_details = {
876
+ "Filename": uploaded_file.name,
877
+ "File size": f"{uploaded_file.size / 1024:.1f} KB",
878
+ "File type": uploaded_file.type
879
+ }
880
+
881
+ with st.expander("File Information", expanded=False):
882
+ for key, value in file_details.items():
883
+ st.write(f"**{key}:** {value}")
884
+
885
  # Extract text based on file type
886
  file_type = uploaded_file.type
887
 
888
+ with st.spinner("Processing your resume..."):
889
  try:
890
  if file_type == "application/pdf":
891
  text = analyzer.extract_text_from_pdf(uploaded_file)
 
898
  return
899
 
900
  if "Error" not in text and text.strip():
901
+ st.success("Resume processed successfully!")
 
902
 
903
  # Store resume context for chatbot
904
  st.session_state.resume_context = text
 
910
  found_keywords, match_percentage = analyzer.keyword_matching(text, selected_role)
911
  ats_score = analyzer.calculate_ats_score(text, sections)
912
 
913
+ # Create navigation tabs
914
+ tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([
915
+ "Summary", "Skills Analysis", "Section Review",
916
+ "ATS Analysis", "Recommendations", "AI Assistant"
917
  ])
918
 
919
  with tab1:
920
+ st.markdown('<h3 class="section-header">Resume Summary</h3>', unsafe_allow_html=True)
921
+
922
+ # Key metrics row
923
+ metric_cols = st.columns(4)
924
+
925
+ with metric_cols[0]:
926
+ display_metric_card("ATS Score", f"{ats_score}/100")
927
 
928
+ with metric_cols[1]:
929
+ display_metric_card("Role Match", f"{match_percentage:.1f}%")
930
+
931
+ with metric_cols[2]:
932
+ word_count = len(text.split())
933
+ display_metric_card("Word Count", f"{word_count}")
934
+
935
+ with metric_cols[3]:
936
+ sections_found = len([s for s in sections.values() if s])
937
+ display_metric_card("Sections", f"{sections_found}/6")
938
+
939
+ st.divider()
940
+
941
+ # Overall assessment
942
+ overall_score = (ats_score + match_percentage) / 2
943
+
944
+ if overall_score >= 80:
945
+ display_alert_box("Excellent resume! Your resume shows strong alignment with the target role and good ATS compatibility.", "success")
946
+ elif overall_score >= 60:
947
+ display_alert_box("Good foundation with room for improvement. Focus on adding more role-specific keywords and optimizing for ATS.", "warning")
948
+ else:
949
+ display_alert_box("Significant improvements needed. Consider restructuring sections, adding relevant keywords, and improving ATS compatibility.", "error")
950
+
951
+ # Quick insights
952
  col1, col2 = st.columns(2)
953
 
954
  with col1:
955
+ st.subheader("Strengths Identified")
956
+ strengths = []
957
+
958
+ if ats_score >= 70:
959
+ strengths.append("Good ATS compatibility")
960
+ if match_percentage >= 60:
961
+ strengths.append("Strong role alignment")
962
+ if len(tech_skills) >= 5:
963
+ strengths.append("Rich technical skills")
964
+ if len(soft_skills) >= 3:
965
+ strengths.append("Good soft skills coverage")
966
 
967
+ if strengths:
968
+ for strength in strengths:
969
+ st.write(f" {strength}")
970
+ else:
971
+ st.write("Focus on the recommendations to improve your resume")
972
 
973
  with col2:
974
+ st.subheader("Keywords Found")
975
  if found_keywords:
976
+ # Display as tags
977
+ keyword_html = ""
978
+ for keyword in found_keywords[:10]: # Show first 10
979
+ keyword_html += f'<span class="skill-tag">{keyword}</span>'
980
+ st.markdown(keyword_html, unsafe_allow_html=True)
981
  else:
982
+ st.write("No role-specific keywords detected")
 
 
 
 
 
 
 
 
983
 
 
984
  with tab2:
985
+ st.markdown('<h3 class="section-header">Skills Analysis</h3>', unsafe_allow_html=True)
986
 
987
  col1, col2 = st.columns(2)
988
 
989
  with col1:
990
+ st.subheader("Technical Skills Detected")
991
  if tech_skills:
992
+ # Create skill tags
993
+ tech_html = ""
994
  for skill in tech_skills:
995
+ tech_html += f'<span class="skill-tag">{skill}</span>'
996
+ st.markdown(tech_html, unsafe_allow_html=True)
997
+
998
+ st.metric("Technical Skills Count", len(tech_skills))
999
  else:
1000
+ display_alert_box("No technical skills detected. Consider adding a dedicated skills section.", "warning")
1001
 
1002
  with col2:
1003
+ st.subheader("Soft Skills Detected")
1004
  if soft_skills:
1005
+ # Create skill tags
1006
+ soft_html = ""
1007
  for skill in soft_skills:
1008
+ soft_html += f'<span class="skill-tag">{skill}</span>'
1009
+ st.markdown(soft_html, unsafe_allow_html=True)
1010
+
1011
+ st.metric("Soft Skills Count", len(soft_skills))
1012
  else:
1013
+ display_alert_box("Limited soft skills detected. Consider highlighting leadership, communication, and teamwork skills.", "info")
1014
 
1015
+ st.divider()
 
 
1016
 
1017
+ # Role-specific analysis
1018
+ st.subheader(f"Analysis for {selected_role}")
1019
+
1020
+ progress_col, details_col = st.columns([1, 2])
1021
+
1022
+ with progress_col:
1023
+ # Create a progress bar for match percentage
1024
+ st.metric("Match Percentage", f"{match_percentage:.1f}%")
1025
+ st.progress(match_percentage / 100)
1026
+
1027
+ with details_col:
1028
+ if match_percentage >= 70:
1029
+ display_alert_box("Excellent match for this role! Your skills align well with industry expectations.", "success")
1030
+ elif match_percentage >= 50:
1031
+ display_alert_box("Good match with opportunities for improvement. Consider adding more role-specific skills.", "warning")
1032
+ else:
1033
+ display_alert_box("Limited match detected. Focus on adding more relevant skills and keywords for this role.", "error")
1034
+
1035
+ # Missing keywords
1036
+ missing_keywords = [kw for kw in analyzer.job_keywords[selected_role]
1037
+ if kw not in found_keywords]
1038
+
1039
+ if missing_keywords:
1040
+ st.subheader("Suggested Keywords to Add")
1041
+ missing_html = ""
1042
+ for keyword in missing_keywords[:15]: # Show top 15
1043
+ missing_html += f'<span class="skill-tag" style="background-color: #fff3cd;">{keyword}</span>'
1044
+ st.markdown(missing_html, unsafe_allow_html=True)
1045
 
1046
  with tab3:
1047
+ st.markdown('<h3 class="section-header">Section Structure Review</h3>', unsafe_allow_html=True)
1048
+
1049
+ # Section status overview
1050
+ st.subheader("Section Completeness")
1051
 
1052
+ section_status = []
1053
  for section_name, section_content in sections.items():
1054
+ status = "Complete" if section_content and len(section_content) > 50 else "Missing/Incomplete"
1055
+ section_status.append({
1056
+ "Section": section_name.title(),
1057
+ "Status": status,
1058
+ "Length": len(section_content) if section_content else 0
1059
+ })
1060
 
1061
+ status_df = pd.DataFrame(section_status)
1062
+ st.dataframe(status_df, use_container_width=True, hide_index=True)
1063
+
1064
+ st.divider()
1065
+
1066
+ # Detailed section content
1067
+ st.subheader("Section Content")
1068
+
1069
+ for section_name, section_content in sections.items():
1070
+ with st.expander(f"{section_name.title()} Section", expanded=False):
1071
+ if section_content:
1072
+ st.text_area(
1073
+ f"{section_name.title()} content:",
1074
+ section_content,
1075
+ height=150,
1076
+ disabled=True,
1077
+ key=f"section_{section_name}"
1078
+ )
1079
+ else:
1080
+ st.warning(f"No {section_name} section found or content is too brief")
1081
 
1082
  with tab4:
1083
+ st.markdown('<h3 class="section-header">ATS Compatibility Analysis</h3>', unsafe_allow_html=True)
1084
 
1085
+ # ATS score breakdown
1086
+ col1, col2 = st.columns([1, 2])
1087
 
1088
  with col1:
1089
+ st.metric("Overall ATS Score", f"{ats_score}/100")
1090
+ st.progress(ats_score / 100)
1091
 
1092
+ # Score interpretation
1093
  if ats_score >= 80:
1094
+ st.success("Excellent ATS compatibility")
1095
  elif ats_score >= 60:
1096
+ st.warning("Good ATS compatibility")
1097
  else:
1098
+ st.error("Needs ATS optimization")
1099
 
1100
  with col2:
1101
+ st.subheader("ATS Checklist")
1102
+
1103
+ # Check various ATS factors
1104
+ checklist_items = []
1105
+
1106
+ # Section check
1107
+ required_sections = ['experience', 'education', 'skills']
1108
+ sections_present = sum(1 for section in required_sections
1109
+ if sections.get(section) and len(sections[section]) > 50)
1110
+ checklist_items.append(("Essential sections present", sections_present >= 2))
1111
+
1112
+ # Content length check
1113
+ word_count = len(text.split())
1114
+ checklist_items.append(("Appropriate length (300-800 words)", 300 <= word_count <= 800))
1115
+
1116
+ # Contact information
1117
+ email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
1118
+ phone_pattern = r'(\+\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}'
1119
+ has_email = bool(re.search(email_pattern, text))
1120
+ has_phone = bool(re.search(phone_pattern, text))
1121
+ checklist_items.append(("Contact information included", has_email or has_phone))
1122
+
1123
+ # Bullet points
1124
+ bullet_patterns = [r'•', r'◦', r'\*', r'-\s', r'→']
1125
+ bullet_count = sum(len(re.findall(pattern, text)) for pattern in bullet_patterns)
1126
+ checklist_items.append(("Uses bullet points", bullet_count >= 2))
1127
+
1128
+ # Keywords
1129
+ checklist_items.append(("Contains relevant keywords", len(found_keywords) >= 3))
1130
+
1131
+ for item, passed in checklist_items:
1132
+ status = "✓" if passed else "✗"
1133
+ color = "green" if passed else "red"
1134
+ st.markdown(f"<span style='color:{color}'>{status} {item}</span>", unsafe_allow_html=True)
1135
+
1136
+ st.divider()
1137
+
1138
+ # Grammar analysis
1139
+ col1, col2 = st.columns(2)
1140
+
1141
+ with col1:
1142
+ st.subheader("Language Quality Check")
1143
  grammar_issues = analyzer.grammar_check(text)
1144
 
1145
  if len(grammar_issues) == 0:
1146
+ display_alert_box("No grammar issues detected", "success")
1147
  else:
1148
+ display_alert_box(f"{len(grammar_issues)} potential issues found", "warning")
1149
+
1150
+ with col2:
1151
+ if grammar_issues:
1152
+ st.subheader("Issues Detected")
1153
+ for issue in grammar_issues[:5]: # Show first 5
1154
+ st.write(f"• {issue.message}")
1155
+
1156
+ # ATS optimization tips
1157
+ st.subheader("ATS Optimization Recommendations")
1158
 
1159
+ tips = [
1160
+ "Use standard section headings: Experience, Education, Skills, etc.",
 
 
1161
  "Include relevant keywords naturally throughout your resume",
1162
+ "Use bullet points to improve readability and scanning",
1163
+ "Avoid images, graphics, tables, and complex formatting",
1164
+ "Use standard fonts like Arial, Calibri, or Times New Roman",
1165
+ "Save your resume as a PDF to preserve formatting",
1166
+ "Include your contact information prominently at the top",
1167
+ "Use consistent formatting throughout the document"
1168
  ]
1169
 
1170
+ for i, tip in enumerate(tips, 1):
1171
+ st.write(f"{i}. {tip}")
1172
 
1173
  with tab5:
1174
+ st.markdown('<h3 class="section-header">Personalized Recommendations</h3>', unsafe_allow_html=True)
1175
 
1176
+ # Generate specific recommendations
 
1177
  recommendations = []
1178
 
1179
+ # ATS-based recommendations
1180
  if ats_score < 70:
1181
  recommendations.extend([
1182
+ "Improve ATS compatibility by adding more bullet points throughout your resume",
1183
+ "Ensure your contact information (email and phone) is clearly visible at the top",
1184
+ "Use standard section headings that ATS systems can easily recognize"
1185
  ])
1186
 
1187
+ # Role matching recommendations
1188
  if match_percentage < 60:
1189
+ recommendations.append(f"Increase your match for {selected_role} by incorporating more industry-specific keywords")
1190
 
1191
+ # Skills recommendations
1192
  if not tech_skills:
1193
+ recommendations.append("Add a dedicated Technical Skills section to highlight your capabilities")
1194
+
1195
+ if not soft_skills:
1196
+ recommendations.append("Incorporate more soft skills like leadership, communication, and teamwork throughout your experience descriptions")
1197
+
1198
+ # Section recommendations
1199
+ missing_sections = [name for name, content in sections.items() if not content]
1200
+ if missing_sections:
1201
+ recommendations.append(f"Consider adding these missing sections: {', '.join(missing_sections)}")
1202
 
1203
  if not sections.get('projects'):
1204
+ recommendations.append("Add a Projects section to showcase hands-on experience and technical skills")
1205
+
1206
+ # Display recommendations
1207
+ if recommendations:
1208
+ for i, rec in enumerate(recommendations, 1):
1209
+ st.write(f"**{i}.** {rec}")
1210
+ else:
1211
+ display_alert_box("Your resume looks great! Minor tweaks based on specific job applications can further improve your success rate.", "success")
1212
 
1213
+ st.divider()
 
1214
 
1215
+ # Action plan
1216
+ st.subheader("Priority Action Plan")
1217
+
1218
+ action_items = []
1219
+
1220
+ if ats_score < 60:
1221
+ action_items.append("**High Priority:** Improve ATS compatibility - focus on formatting and standard sections")
1222
+
1223
+ if match_percentage < 50:
1224
+ action_items.append("**High Priority:** Add more role-specific keywords and skills")
1225
+
1226
+ if not tech_skills and selected_role in ["Data Scientist", "Software Engineer", "DevOps Engineer"]:
1227
+ action_items.append("**Medium Priority:** Add comprehensive technical skills section")
1228
+
1229
+ if len([s for s in sections.values() if s]) < 4:
1230
+ action_items.append("**Medium Priority:** Ensure all essential sections are complete and substantial")
1231
+
1232
+ action_items.append("**Ongoing:** Customize your resume for each job application by matching keywords")
1233
+
1234
+ for action in action_items:
1235
+ st.markdown(action)
1236
+
1237
+ st.divider()
1238
+
1239
+ # PDF report generation
1240
+ st.subheader("Download Detailed Report")
1241
+
1242
+ col1, col2 = st.columns([2, 1])
1243
+
1244
+ with col1:
1245
+ st.write("Generate a comprehensive PDF report with all analysis results, recommendations, and action items.")
1246
+
1247
+ with col2:
1248
+ if st.button("Generate PDF Report", type="primary", use_container_width=True):
1249
+ try:
1250
+ with st.spinner("Generating report..."):
1251
+ pdf_buffer = analyzer.create_pdf_report(
1252
+ text, sections, ats_score, match_percentage,
1253
+ selected_role, tech_skills, soft_skills, found_keywords
1254
+ )
1255
+
1256
+ st.download_button(
1257
+ label="Download Report",
1258
+ data=pdf_buffer.getvalue(),
1259
+ file_name=f"resume_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf",
1260
+ mime="application/pdf",
1261
+ use_container_width=True
1262
+ )
1263
+ except Exception as e:
1264
+ st.error(f"Error generating PDF: {str(e)}")
1265
 
1266
+ with tab6:
1267
+ # AI Chat Interface
1268
+ create_improved_chat_interface(st.session_state.get('resume_context', ''))
 
1269
 
1270
  except Exception as e:
1271
  st.error(f"Error during analysis: {str(e)}")
1272
  st.error("Please check your resume format and try again.")
1273
 
1274
  else:
1275
+ st.error("Could not extract text from the uploaded file. Please check the file format and try again.")
1276
 
1277
  else:
1278
+ # Instructions and information when no file is uploaded
1279
+ st.markdown('<h3 class="section-header">Getting Started</h3>', unsafe_allow_html=True)
1280
 
1281
+ col1, col2 = st.columns([2, 1])
1282
+
1283
+ with col1:
1284
+ st.subheader("How It Works")
1285
  st.markdown("""
1286
+ **1. Upload Your Resume**
1287
+ Upload your resume in PDF, DOCX, or TXT format using the file uploader above.
1288
+
1289
+ **2. Select Target Role**
1290
+ Choose your target job role from the sidebar to get relevant keyword analysis.
1291
+
1292
+ **3. Get Comprehensive Analysis**
1293
+ Review detailed analysis across multiple categories including ATS compatibility, skills matching, and section structure.
1294
+
1295
+ **4. Get AI-Powered Recommendations**
1296
+ Use the AI assistant for personalized advice and answers to your specific questions.
1297
+
1298
+ **5. Download Report**
1299
+ Generate and download a comprehensive PDF report with all findings and recommendations.
1300
+ """)
1301
+
1302
+ with col2:
1303
+ st.subheader("Analysis Features")
1304
+
1305
+ features = [
1306
+ "ATS Compatibility Scoring",
1307
+ "Skills Detection & Matching",
1308
+ "Keyword Optimization",
1309
+ "Section Structure Analysis",
1310
+ "Grammar & Language Check",
1311
+ "Role-Specific Recommendations",
1312
+ "AI-Powered Chat Assistant",
1313
+ "Downloadable PDF Reports"
1314
+ ]
1315
+
1316
+ for feature in features:
1317
+ st.write(f"✓ {feature}")
1318
+
1319
+ # Sample analysis preview
1320
+ with st.expander("Preview: What You'll Get", expanded=False):
1321
+ st.subheader("Sample Analysis Output")
1322
+
1323
+ sample_cols = st.columns(3)
1324
 
1325
+ with sample_cols[0]:
1326
+ display_metric_card("ATS Score", "85/100")
 
 
 
 
1327
 
1328
+ with sample_cols[1]:
1329
+ display_metric_card("Role Match", "72%")
1330
 
1331
+ with sample_cols[2]:
1332
+ display_metric_card("Overall", "A-")
1333
+
1334
+ st.markdown("""
1335
+ **You'll receive detailed analysis including:**
1336
+ - Comprehensive scoring and metrics
1337
+ - Section-by-section breakdown
1338
+ - Specific improvement recommendations
1339
+ - Missing keywords identification
1340
+ - ATS optimization checklist
1341
+ - AI-powered personalized advice
1342
  """)
1343
 
1344
  if __name__ == "__main__":