ZainabFatimaa commited on
Commit
25ae2d0
Β·
verified Β·
1 Parent(s): 0d2a01d

Update src/app.py

Browse files
Files changed (1) hide show
  1. src/app.py +209 -327
src/app.py CHANGED
@@ -60,27 +60,20 @@ from reportlab.lib.styles import getSampleStyleSheet
60
  from reportlab.lib.units import inch
61
 
62
  # Claude Chatbot Class
63
- import time
64
-
65
  class ClaudeChatbot:
66
  def __init__(self):
67
  self.api_key = os.getenv('OPENROUTER_API_KEY')
68
  self.base_url = "https://openrouter.ai/api/v1/chat/completions"
69
 
70
- # IMPORTANT: Claude Sonnet 4 is NOT free - use a free model instead
71
- # Free models on OpenRouter include:
72
- self.model = "meta-llama/llama-3.2-3b-instruct:free" # Free alternative
73
- # Other free options:
74
- # "microsoft/phi-3-mini-128k-instruct:free"
75
- # "huggingface/zephyr-7b-beta:free"
76
- # "mistralai/mistral-7b-instruct:free"
77
 
78
  if not self.api_key:
79
  st.error("❌ OPENROUTER_API_KEY not found in environment variables!")
80
  st.info("Please set your OpenRouter API key in the environment variables.")
81
 
82
  def generate_response(self, prompt, context="", max_tokens=1500):
83
- """Generate response using free models via OpenRouter"""
84
  if not self.api_key:
85
  return "Error: API key not configured"
86
 
@@ -126,11 +119,7 @@ class ClaudeChatbot:
126
  try:
127
  response = requests.post(self.base_url, headers=headers, json=data, timeout=30)
128
 
129
- if response.status_code == 429:
130
- st.warning("Rate limit hit. Waiting 60 seconds...")
131
- time.sleep(60)
132
- response = requests.post(self.base_url, headers=headers, json=data, timeout=30)
133
-
134
  if response.status_code == 402:
135
  return "Error: This model requires payment. Please switch to a free model or add credits to your OpenRouter account."
136
 
@@ -146,7 +135,7 @@ class ClaudeChatbot:
146
  if e.response.status_code == 402:
147
  return "Error: Payment required. This model is not free. Please use a free model or add credits."
148
  elif e.response.status_code == 429:
149
- return "Error: Rate limit exceeded. Please try again later."
150
  else:
151
  return f"HTTP Error: {e.response.status_code} - {str(e)}"
152
  except requests.exceptions.RequestException as e:
@@ -283,6 +272,18 @@ class ResumeAnalyzer:
283
  "business process", "gap analysis", "user stories", "workflow", "project management"],
284
  "Full Stack Developer": ["html", "css", "javascript", "react", "angular", "vue", "node.js", "express",
285
  "mongodb", "postgresql", "rest api", "graphql", "version control", "responsive design"],
 
 
 
 
 
 
 
 
 
 
 
 
286
  }
287
 
288
  # Common skills database
@@ -630,19 +631,6 @@ def main():
630
  # Initialize analyzer
631
  try:
632
  analyzer = ResumeAnalyzer()
633
- # Add ML/AI Engineer to job keywords
634
- analyzer.job_keywords["Machine Learning Engineer"] = [
635
- "python", "tensorflow", "pytorch", "scikit-learn", "pandas", "numpy", "machine learning",
636
- "deep learning", "neural networks", "computer vision", "nlp", "data science", "algorithms",
637
- "statistics", "linear algebra", "calculus", "regression", "classification", "clustering",
638
- "feature engineering", "model deployment", "mlops", "docker", "kubernetes", "aws", "gcp"
639
- ]
640
- analyzer.job_keywords["AI Engineer"] = [
641
- "artificial intelligence", "machine learning", "deep learning", "neural networks", "python",
642
- "tensorflow", "pytorch", "computer vision", "nlp", "natural language processing", "opencv",
643
- "transformers", "bert", "gpt", "reinforcement learning", "generative ai", "llm", "chatbot",
644
- "model optimization", "ai ethics", "edge ai", "quantization", "onnx", "tensorrt"
645
- ]
646
  except Exception as e:
647
  st.error(f"Error initializing analyzer: {str(e)}")
648
  return
@@ -652,15 +640,15 @@ def main():
652
  job_roles = list(analyzer.job_keywords.keys())
653
  selected_role = st.sidebar.selectbox("Select Target Job Role:", job_roles)
654
 
655
- # Initialize session state for chat - FIXED
656
  if "chat_history" not in st.session_state:
657
  st.session_state.chat_history = []
658
  if "resume_context" not in st.session_state:
659
  st.session_state.resume_context = ""
660
  if "show_chat" not in st.session_state:
661
  st.session_state.show_chat = False
662
- if "chat_processed" not in st.session_state:
663
- st.session_state.chat_processed = False
664
 
665
  # File upload section
666
  st.header("πŸ“ Upload Your Resume")
@@ -723,29 +711,20 @@ def main():
723
  with col2:
724
  # Generate persona summary
725
  persona_summary = analyzer.generate_persona_summary(text, sections)
726
- st.subheader("🎭 AI Persona Summary")
727
- st.info(persona_summary)
728
-
729
- # Word cloud
730
- st.subheader("☁️ Word Cloud")
731
- preprocessed_tokens = analyzer.preprocess_text(text)
732
- if preprocessed_tokens:
733
- wordcloud_text = ' '.join(preprocessed_tokens)
734
- try:
735
- wordcloud = WordCloud(width=800, height=400, background_color='white').generate(wordcloud_text)
736
-
737
- fig, ax = plt.subplots(figsize=(12, 6))
738
- ax.imshow(wordcloud, interpolation='bilinear')
739
- ax.axis('off')
740
- st.pyplot(fig)
741
- except Exception as e:
742
- st.warning("Could not generate word cloud. Showing top words instead.")
743
- word_freq = Counter(preprocessed_tokens)
744
- top_words = word_freq.most_common(20)
745
-
746
- words_df = pd.DataFrame(top_words, columns=['Word', 'Frequency'])
747
- fig = px.bar(words_df, x='Word', y='Frequency', title='Top 20 Words')
748
- st.plotly_chart(fig)
749
 
750
  with tab2:
751
  st.header("Skills Analysis")
@@ -753,100 +732,70 @@ def main():
753
  col1, col2 = st.columns(2)
754
 
755
  with col1:
756
- st.subheader("πŸ”§ Technical Skills")
757
  if tech_skills:
758
- skills_text = " β€’ ".join(tech_skills)
759
- st.success(f"Found {len(tech_skills)} technical skills:")
760
- st.write(skills_text)
761
-
762
- if len(tech_skills) > 5:
763
- skills_df = pd.DataFrame({
764
- 'Skill': tech_skills[:10],
765
- 'Count': [1] * len(tech_skills[:10])
766
- })
767
- fig = px.pie(skills_df, values='Count', names='Skill',
768
- title='Technical Skills Distribution')
769
- st.plotly_chart(fig, use_container_width=True)
770
- else:
771
- skills_df = pd.DataFrame({
772
- 'Skill': tech_skills,
773
- 'Count': [1] * len(tech_skills)
774
- })
775
- fig = px.bar(skills_df, x='Skill', y='Count',
776
- title='Technical Skills Found')
777
- fig.update_xaxis(tickangle=45)
778
- st.plotly_chart(fig, use_container_width=True)
779
  else:
780
- st.warning("No technical skills detected")
781
- st.info("πŸ’‘ Consider adding technical skills relevant to your field")
782
 
783
  with col2:
784
- st.subheader("🀝 Soft Skills")
785
  if soft_skills:
786
- skills_text = " β€’ ".join(soft_skills)
787
- st.success(f"Found {len(soft_skills)} soft skills:")
788
- st.write(skills_text)
789
-
790
- if len(soft_skills) > 3:
791
- soft_df = pd.DataFrame({
792
- 'Skill': soft_skills[:8],
793
- 'Count': [1] * len(soft_skills[:8])
794
- })
795
- fig = px.bar(soft_df, x='Skill', y='Count',
796
- title='Soft Skills Found',
797
- color='Skill')
798
- fig.update_xaxis(tickangle=45)
799
- st.plotly_chart(fig, use_container_width=True)
800
  else:
801
- st.warning("No soft skills detected")
802
- st.info("πŸ’‘ Consider highlighting soft skills like leadership, communication, teamwork")
803
 
804
  # Role-specific keyword analysis
805
- st.subheader(f"🎯 {selected_role} Keywords")
806
- if found_keywords:
807
- st.success(f"Found {len(found_keywords)} relevant keywords for {selected_role}:")
808
- keywords_text = " β€’ ".join(found_keywords)
809
- st.write(keywords_text)
810
- st.info(f"Match Percentage: {match_percentage:.1f}%")
811
 
812
- # Progress bar for match percentage
813
- st.progress(match_percentage / 100)
814
- else:
815
- st.warning(f"No {selected_role}-specific keywords found")
816
- missing_keywords = [kw for kw in analyzer.job_keywords[selected_role] if kw not in text.lower()]
817
- if missing_keywords:
818
- st.info(f"πŸ’‘ Consider adding these keywords: {', '.join(missing_keywords[:5])}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
819
 
820
  with tab3:
821
  st.header("Section Breakdown")
822
 
823
  for section_name, section_content in sections.items():
824
  if section_content:
825
- with st.expander(f"πŸ“‘ {section_name.title()} Section"):
826
- st.text_area(
827
- f"{section_name.title()} Content",
828
- section_content,
829
- height=200,
830
- key=f"section_{section_name}"
831
- )
832
-
833
- # Section-specific analysis
834
- word_count = len(section_content.split())
835
- st.metric(f"{section_name.title()} Word Count", word_count)
836
-
837
- if section_name == "experience":
838
- # Analyze experience section
839
- years_mentioned = len(re.findall(r'\b(19|20)\d{2}\b', section_content))
840
- companies_mentioned = len(re.findall(r'\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b', section_content))
841
- st.metric("Years/Dates Mentioned", years_mentioned)
842
- st.metric("Potential Companies", companies_mentioned)
843
-
844
- elif section_name == "education":
845
- # Analyze education section
846
- degrees = re.findall(r'\b(bachelor|master|phd|degree|diploma|certificate)\b', section_content.lower())
847
- st.metric("Degrees/Certificates Found", len(degrees))
848
  else:
849
  st.warning(f"⚠️ {section_name.title()} section not found or empty")
 
 
 
 
 
 
 
 
 
 
850
 
851
  with tab4:
852
  st.header("ATS Analysis")
@@ -854,106 +803,60 @@ def main():
854
  col1, col2 = st.columns(2)
855
 
856
  with col1:
857
- st.subheader("πŸ“Š ATS Score Breakdown")
858
- st.metric("Overall ATS Score", f"{ats_score}/100")
859
 
860
- # ATS score visualization
861
- fig = go.Figure(go.Indicator(
862
- mode = "gauge+number+delta",
863
- value = ats_score,
864
- domain = {'x': [0, 1], 'y': [0, 1]},
865
- title = {'text': "ATS Score"},
866
- delta = {'reference': 70},
867
- gauge = {
868
- 'axis': {'range': [None, 100]},
869
- 'bar': {'color': "darkblue"},
870
- 'steps': [
871
- {'range': [0, 50], 'color': "lightgray"},
872
- {'range': [50, 70], 'color': "yellow"},
873
- {'range': [70, 100], 'color': "green"}
874
- ],
875
- 'threshold': {
876
- 'line': {'color': "red", 'width': 4},
877
- 'thickness': 0.75,
878
- 'value': 90
879
- }
880
- }
881
- ))
882
- st.plotly_chart(fig, use_container_width=True)
883
 
884
  with col2:
885
- st.subheader("🎯 Role Match Analysis")
886
- st.metric("Role Match Score", f"{match_percentage:.1f}%")
887
-
888
- # Combined score
889
- combined_score = (ats_score + match_percentage) / 2
890
- st.metric("Combined Score", f"{combined_score:.1f}/100")
891
 
892
- # Score interpretation
893
- if combined_score >= 80:
894
- st.success("πŸŽ‰ Excellent! Your resume is well-optimized")
895
- elif combined_score >= 60:
896
- st.warning("πŸ‘ Good, but room for improvement")
897
  else:
898
- st.error("⚠️ Needs significant improvement")
 
 
 
899
 
900
- # Grammar check
901
- st.subheader("πŸ“ Grammar & Language Quality")
902
- with st.spinner("Checking grammar..."):
903
- grammar_issues = analyzer.grammar_check(text)
 
 
 
 
 
 
 
904
 
905
- if grammar_issues:
906
- st.warning(f"Found {len(grammar_issues)} potential grammar issues:")
907
- for i, issue in enumerate(grammar_issues[:10]): # Show first 10 issues
908
- if hasattr(issue, 'message'):
909
- st.write(f"β€’ {issue.message}")
910
- else:
911
- st.write(f"β€’ {str(issue)}")
912
-
913
- if len(grammar_issues) > 10:
914
- st.info(f"... and {len(grammar_issues) - 10} more issues")
915
- else:
916
- st.success("βœ… No major grammar issues detected!")
917
 
918
  with tab5:
919
- st.header("Report & Suggestions")
920
 
921
- # Get AI analysis from Claude
922
  if os.getenv('OPENROUTER_API_KEY'):
923
- st.subheader("πŸ€– AI-Powered Analysis")
924
- with st.spinner("Getting AI analysis from Claude..."):
925
  claude_analysis = analyzer.get_claude_analysis(
926
  text, sections, selected_role, ats_score, match_percentage
927
  )
928
- st.markdown(claude_analysis)
 
 
929
  else:
930
- st.info("Claude API not available. Showing basic recommendations.")
931
-
932
- # Basic recommendations
933
- st.subheader("πŸ“‹ Quick Recommendations")
934
- recommendations = []
935
 
936
- if ats_score < 70:
937
- recommendations.append("πŸ”Ή Improve ATS compatibility by adding more bullet points and clear section headers")
938
-
939
- if match_percentage < 60:
940
- recommendations.append(f"πŸ”Ή Add more {selected_role}-specific keywords to improve role match")
941
-
942
- if len(tech_skills) < 5:
943
- recommendations.append("πŸ”Ή Include more technical skills relevant to your field")
944
-
945
- if not sections.get('projects'):
946
- recommendations.append("πŸ”Ή Consider adding a projects section to showcase your work")
947
-
948
- if len(text.split()) < 300:
949
- recommendations.append("πŸ”Ή Expand your resume content - it seems too brief")
950
- elif len(text.split()) > 800:
951
- recommendations.append("πŸ”Ή Consider condensing your resume - it might be too lengthy")
952
-
953
- for rec in recommendations:
954
- st.markdown(rec)
955
-
956
- # PDF Report Generation
957
  st.subheader("πŸ“„ Download Report")
958
  if st.button("Generate PDF Report"):
959
  try:
@@ -964,135 +867,114 @@ def main():
964
 
965
  st.download_button(
966
  label="πŸ“₯ Download PDF Report",
967
- data=pdf_buffer,
968
  file_name=f"resume_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf",
969
  mime="application/pdf"
970
  )
971
  except Exception as e:
972
  st.error(f"Error generating PDF: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
973
 
974
- # AI Assistant Chat Section (Outside of tabs) - COMPLETELY FIXED
975
- st.markdown("---")
976
- st.header("πŸ€– AI Assistant Chat")
977
 
978
  # Toggle chat visibility
979
- chat_toggle = st.button("πŸ’¬ Toggle AI Assistant Chat", key="main_chat_toggle")
980
- if chat_toggle:
981
  st.session_state.show_chat = not st.session_state.show_chat
982
 
983
  if st.session_state.show_chat:
984
- if not os.getenv('OPENROUTER_API_KEY'):
985
- st.error("Claude API key not configured. Please set OPENROUTER_API_KEY environment variable.")
986
- else:
987
- # Create analysis context for better responses
988
- analysis_context = f"""
989
- Resume Analysis Results:
990
- - Target Role: {selected_role}
991
- - ATS Score: {ats_score}/100
992
- - Role Match: {match_percentage:.1f}%
993
- - Word Count: {len(text.split())}
994
- - Technical Skills Found: {', '.join(tech_skills) if tech_skills else 'None detected'}
995
- - Soft Skills Found: {', '.join(soft_skills) if soft_skills else 'None detected'}
996
- - Keywords Found: {', '.join(found_keywords) if found_keywords else 'None detected'}
997
- - Sections Present: {', '.join([k for k, v in sections.items() if v])}
998
- """
999
-
1000
- # Display chat history
1001
- for chat in st.session_state.chat_history:
1002
- with st.chat_message(chat["role"]):
1003
- st.markdown(chat["content"])
1004
-
1005
- # Suggested questions
1006
- st.subheader("πŸ’‘ Suggested Questions")
1007
- col1, col2, col3, col4 = st.columns(4)
1008
-
1009
- with col1:
1010
- if st.button("How can I improve my resume?", key="improve_btn"):
1011
- prompt = "Based on my resume analysis, how can I improve my resume to get better ATS scores and job match rates?"
1012
- with st.chat_message("user"):
1013
- st.markdown(prompt)
1014
- st.session_state.chat_history.append({"role": "user", "content": prompt})
1015
-
1016
- with st.chat_message("assistant"):
1017
- with st.spinner("Analyzing your resume..."):
1018
- response = analyzer.chatbot.generate_response(prompt, analysis_context + st.session_state.resume_context)
1019
- st.markdown(response)
1020
- st.session_state.chat_history.append({"role": "assistant", "content": response})
1021
-
1022
- with col2:
1023
- if st.button("What skills should I add?", key="skills_btn"):
1024
- prompt = f"What specific technical and soft skills should I add to my resume for a {selected_role} position based on my current analysis?"
1025
- with st.chat_message("user"):
1026
- st.markdown(prompt)
1027
- st.session_state.chat_history.append({"role": "user", "content": prompt})
1028
-
1029
- with st.chat_message("assistant"):
1030
- with st.spinner("Analyzing skill gaps..."):
1031
- response = analyzer.chatbot.generate_response(prompt, analysis_context + st.session_state.resume_context)
1032
- st.markdown(response)
1033
- st.session_state.chat_history.append({"role": "assistant", "content": response})
1034
-
1035
- with col3:
1036
- if st.button("Format suggestions?", key="format_btn"):
1037
- prompt = "What formatting improvements can I make to my resume to make it more ATS-friendly and visually appealing?"
1038
- with st.chat_message("user"):
1039
- st.markdown(prompt)
1040
- st.session_state.chat_history.append({"role": "user", "content": prompt})
1041
-
1042
- with st.chat_message("assistant"):
1043
- with st.spinner("Analyzing format..."):
1044
- response = analyzer.chatbot.generate_response(prompt, analysis_context + st.session_state.resume_context)
1045
- st.markdown(response)
1046
- st.session_state.chat_history.append({"role": "assistant", "content": response})
1047
-
1048
- with col4:
1049
- if st.button("πŸ—‘οΈ Clear Chat", key="clear_btn"):
1050
- st.session_state.chat_history = []
1051
- st.rerun()
1052
-
1053
- # Chat input - MAIN FIX: Handle chat without multiple responses
1054
- user_prompt = st.chat_input("Ask me anything about your resume...")
1055
 
1056
- if user_prompt and user_prompt.strip():
1057
- # Display user message
1058
- with st.chat_message("user"):
1059
- st.markdown(user_prompt)
1060
- st.session_state.chat_history.append({"role": "user", "content": user_prompt})
1061
 
1062
- # Generate and display assistant response
1063
- with st.chat_message("assistant"):
1064
- with st.spinner("Thinking..."):
1065
- response = analyzer.chatbot.generate_response(
1066
- user_prompt,
1067
- analysis_context + st.session_state.resume_context
1068
- )
1069
- st.markdown(response)
1070
- st.session_state.chat_history.append({"role": "assistant", "content": response})
1071
-
 
 
 
 
 
 
 
 
 
 
 
 
 
1072
  except Exception as e:
1073
  st.error(f"Error during analysis: {str(e)}")
 
 
1074
  else:
1075
- st.error("Could not extract text from the uploaded file. Please try a different file.")
1076
-
1077
- # Footer
1078
- st.markdown("---")
1079
- st.markdown("### πŸ“ Tips for Better Resume Analysis")
1080
- st.markdown("""
1081
- - **Upload clear, well-formatted documents** for better text extraction
1082
- - **Select the appropriate job role** to get relevant keyword matching
1083
- - **Use the AI Assistant** to get personalized advice
1084
- - **Download the PDF report** for offline reference
1085
- - **Check multiple job roles** to see how your resume performs across different positions
1086
- """)
1087
 
1088
- # Instructions for API setup
1089
- if not os.getenv('OPENROUTER_API_KEY'):
1090
- with st.expander("πŸ”§ Setup Instructions for Claude AI"):
 
 
 
1091
  st.markdown("""
1092
- To enable the AI Assistant feature:
1093
- 1. Get an API key from [OpenRouter](https://openrouter.ai/)
1094
- 2. Set the environment variable: `OPENROUTER_API_KEY=your_key_here`
1095
- 3. Restart the application
 
 
 
 
 
 
1096
  """)
1097
 
1098
  if __name__ == "__main__":
 
60
  from reportlab.lib.units import inch
61
 
62
  # Claude Chatbot Class
 
 
63
  class ClaudeChatbot:
64
  def __init__(self):
65
  self.api_key = os.getenv('OPENROUTER_API_KEY')
66
  self.base_url = "https://openrouter.ai/api/v1/chat/completions"
67
 
68
+ # Using free models on OpenRouter
69
+ self.model = "meta-llama/llama-3.2-3b-instruct:free"
 
 
 
 
 
70
 
71
  if not self.api_key:
72
  st.error("❌ OPENROUTER_API_KEY not found in environment variables!")
73
  st.info("Please set your OpenRouter API key in the environment variables.")
74
 
75
  def generate_response(self, prompt, context="", max_tokens=1500):
76
+ """Generate response using free models via OpenRouter - No rate limiting"""
77
  if not self.api_key:
78
  return "Error: API key not configured"
79
 
 
119
  try:
120
  response = requests.post(self.base_url, headers=headers, json=data, timeout=30)
121
 
122
+ # Handle payment required error
 
 
 
 
123
  if response.status_code == 402:
124
  return "Error: This model requires payment. Please switch to a free model or add credits to your OpenRouter account."
125
 
 
135
  if e.response.status_code == 402:
136
  return "Error: Payment required. This model is not free. Please use a free model or add credits."
137
  elif e.response.status_code == 429:
138
+ return "Error: Too many requests. Please try again in a moment."
139
  else:
140
  return f"HTTP Error: {e.response.status_code} - {str(e)}"
141
  except requests.exceptions.RequestException as e:
 
272
  "business process", "gap analysis", "user stories", "workflow", "project management"],
273
  "Full Stack Developer": ["html", "css", "javascript", "react", "angular", "vue", "node.js", "express",
274
  "mongodb", "postgresql", "rest api", "graphql", "version control", "responsive design"],
275
+ "Machine Learning Engineer": [
276
+ "python", "tensorflow", "pytorch", "scikit-learn", "pandas", "numpy", "machine learning",
277
+ "deep learning", "neural networks", "computer vision", "nlp", "data science", "algorithms",
278
+ "statistics", "linear algebra", "calculus", "regression", "classification", "clustering",
279
+ "feature engineering", "model deployment", "mlops", "docker", "kubernetes", "aws", "gcp"
280
+ ],
281
+ "AI Engineer": [
282
+ "artificial intelligence", "machine learning", "deep learning", "neural networks", "python",
283
+ "tensorflow", "pytorch", "computer vision", "nlp", "natural language processing", "opencv",
284
+ "transformers", "bert", "gpt", "reinforcement learning", "generative ai", "llm", "chatbot",
285
+ "model optimization", "ai ethics", "edge ai", "quantization", "onnx", "tensorrt"
286
+ ]
287
  }
288
 
289
  # Common skills database
 
631
  # Initialize analyzer
632
  try:
633
  analyzer = ResumeAnalyzer()
 
 
 
 
 
 
 
 
 
 
 
 
 
634
  except Exception as e:
635
  st.error(f"Error initializing analyzer: {str(e)}")
636
  return
 
640
  job_roles = list(analyzer.job_keywords.keys())
641
  selected_role = st.sidebar.selectbox("Select Target Job Role:", job_roles)
642
 
643
+ # Initialize session state for chat
644
  if "chat_history" not in st.session_state:
645
  st.session_state.chat_history = []
646
  if "resume_context" not in st.session_state:
647
  st.session_state.resume_context = ""
648
  if "show_chat" not in st.session_state:
649
  st.session_state.show_chat = False
650
+ if "waiting_for_response" not in st.session_state:
651
+ st.session_state.waiting_for_response = False
652
 
653
  # File upload section
654
  st.header("πŸ“ Upload Your Resume")
 
711
  with col2:
712
  # Generate persona summary
713
  persona_summary = analyzer.generate_persona_summary(text, sections)
714
+ st.subheader("AI-Generated Persona")
715
+ st.write(persona_summary)
716
+
717
+ # Overall scores
718
+ overall_score = (ats_score + match_percentage) / 2
719
+ st.metric("Overall Score", f"{overall_score:.1f}/100")
720
+
721
+ # Score interpretation
722
+ if overall_score >= 80:
723
+ st.success("πŸŽ‰ Excellent! Your resume is well-optimized.")
724
+ elif overall_score >= 60:
725
+ st.warning("⚠️ Good, but there's room for improvement.")
726
+ else:
727
+ st.error("❌ Needs significant improvement.")
 
 
 
 
 
 
 
 
 
728
 
729
  with tab2:
730
  st.header("Skills Analysis")
 
732
  col1, col2 = st.columns(2)
733
 
734
  with col1:
735
+ st.subheader("Technical Skills Found")
736
  if tech_skills:
737
+ for skill in tech_skills:
738
+ st.write(f"βœ… {skill}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
739
  else:
740
+ st.write("No technical skills detected. Consider adding relevant technical skills.")
 
741
 
742
  with col2:
743
+ st.subheader("Soft Skills Found")
744
  if soft_skills:
745
+ for skill in soft_skills:
746
+ st.write(f"βœ… {skill}")
 
 
 
 
 
 
 
 
 
 
 
 
747
  else:
748
+ st.write("No soft skills detected. Consider highlighting your interpersonal abilities.")
 
749
 
750
  # Role-specific keyword analysis
751
+ st.subheader(f"Keywords for {selected_role}")
752
+ col1, col2 = st.columns(2)
753
+
754
+ with col1:
755
+ st.metric("Match Percentage", f"{match_percentage:.1f}%")
 
756
 
757
+ if match_percentage >= 70:
758
+ st.success("🎯 Great keyword match!")
759
+ elif match_percentage >= 50:
760
+ st.warning("⚠️ Moderate keyword match")
761
+ else:
762
+ st.error("❌ Low keyword match")
763
+
764
+ with col2:
765
+ st.write("**Found Keywords:**")
766
+ if found_keywords:
767
+ for keyword in found_keywords:
768
+ st.write(f"βœ… {keyword}")
769
+ else:
770
+ st.write("No role-specific keywords found.")
771
+
772
+ # Missing keywords
773
+ missing_keywords = [kw for kw in analyzer.job_keywords[selected_role]
774
+ if kw not in found_keywords]
775
+ if missing_keywords:
776
+ st.subheader("Suggested Keywords to Add")
777
+ for keyword in missing_keywords[:10]: # Show top 10
778
+ st.write(f"βž• {keyword}")
779
 
780
  with tab3:
781
  st.header("Section Breakdown")
782
 
783
  for section_name, section_content in sections.items():
784
  if section_content:
785
+ with st.expander(f"πŸ“‹ {section_name.title()} Section"):
786
+ st.write(section_content)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
787
  else:
788
  st.warning(f"⚠️ {section_name.title()} section not found or empty")
789
+
790
+ # Section recommendations
791
+ st.subheader("Section Recommendations")
792
+ missing_sections = [name for name, content in sections.items() if not content]
793
+ if missing_sections:
794
+ st.write("**Missing or Empty Sections:**")
795
+ for section in missing_sections:
796
+ st.write(f"❌ {section.title()}")
797
+ else:
798
+ st.success("βœ… All major sections are present!")
799
 
800
  with tab4:
801
  st.header("ATS Analysis")
 
803
  col1, col2 = st.columns(2)
804
 
805
  with col1:
806
+ st.metric("ATS Score", f"{ats_score}/100")
 
807
 
808
+ # ATS Score interpretation
809
+ if ats_score >= 80:
810
+ st.success("πŸ€– Excellent ATS compatibility!")
811
+ elif ats_score >= 60:
812
+ st.warning("⚠️ Good ATS compatibility")
813
+ else:
814
+ st.error("❌ Poor ATS compatibility")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
815
 
816
  with col2:
817
+ # Grammar check
818
+ st.subheader("Grammar Check")
819
+ grammar_issues = analyzer.grammar_check(text)
 
 
 
820
 
821
+ if len(grammar_issues) == 0:
822
+ st.success("βœ… No grammar issues detected!")
 
 
 
823
  else:
824
+ st.warning(f"⚠️ {len(grammar_issues)} potential issues found")
825
+ with st.expander("View Grammar Issues"):
826
+ for issue in grammar_issues[:10]: # Show first 10
827
+ st.write(f"β€’ {issue.message}")
828
 
829
+ # ATS Tips
830
+ st.subheader("ATS Optimization Tips")
831
+ ats_tips = [
832
+ "Use standard section headings (Experience, Education, Skills)",
833
+ "Include relevant keywords naturally throughout your resume",
834
+ "Use bullet points for easy scanning",
835
+ "Avoid images, tables, and complex formatting",
836
+ "Use standard fonts (Arial, Calibri, Times New Roman)",
837
+ "Save as PDF to preserve formatting",
838
+ "Include contact information at the top"
839
+ ]
840
 
841
+ for tip in ats_tips:
842
+ st.write(f"πŸ’‘ {tip}")
 
 
 
 
 
 
 
 
 
 
843
 
844
  with tab5:
845
+ st.header("Comprehensive Analysis & Suggestions")
846
 
847
+ # Get Claude analysis if API is available
848
  if os.getenv('OPENROUTER_API_KEY'):
849
+ with st.spinner("Getting AI-powered analysis..."):
 
850
  claude_analysis = analyzer.get_claude_analysis(
851
  text, sections, selected_role, ats_score, match_percentage
852
  )
853
+
854
+ st.subheader("πŸ€– AI-Powered Analysis")
855
+ st.write(claude_analysis)
856
  else:
857
+ st.warning("⚠️ AI analysis not available. Please set OPENROUTER_API_KEY.")
 
 
 
 
858
 
859
+ # Generate PDF report
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
860
  st.subheader("πŸ“„ Download Report")
861
  if st.button("Generate PDF Report"):
862
  try:
 
867
 
868
  st.download_button(
869
  label="πŸ“₯ Download PDF Report",
870
+ data=pdf_buffer.getvalue(),
871
  file_name=f"resume_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf",
872
  mime="application/pdf"
873
  )
874
  except Exception as e:
875
  st.error(f"Error generating PDF: {str(e)}")
876
+
877
+ # Manual recommendations
878
+ st.subheader("πŸ“ Quick Recommendations")
879
+ recommendations = []
880
+
881
+ if ats_score < 70:
882
+ recommendations.extend([
883
+ "Add more bullet points to improve readability",
884
+ "Ensure contact information is clearly visible",
885
+ "Use standard section headings"
886
+ ])
887
+
888
+ if match_percentage < 60:
889
+ recommendations.append(f"Include more {selected_role}-specific keywords")
890
+
891
+ if not tech_skills:
892
+ recommendations.append("Add a dedicated technical skills section")
893
+
894
+ if not sections.get('projects'):
895
+ recommendations.append("Consider adding a projects section")
896
+
897
+ for i, rec in enumerate(recommendations, 1):
898
+ st.write(f"{i}. {rec}")
899
 
900
+ # Chat Interface
901
+ st.header("πŸ’¬ Chat with Resume Assistant")
 
902
 
903
  # Toggle chat visibility
904
+ if st.button("πŸ€– Ask Questions About Your Resume"):
 
905
  st.session_state.show_chat = not st.session_state.show_chat
906
 
907
  if st.session_state.show_chat:
908
+ # Model selection
909
+ available_models = analyzer.chatbot.get_available_free_models()
910
+ selected_model = st.selectbox(
911
+ "Choose AI Model:",
912
+ available_models,
913
+ index=0 if analyzer.chatbot.model in available_models else 0
914
+ )
915
+
916
+ if selected_model != analyzer.chatbot.model:
917
+ analyzer.chatbot.switch_model(selected_model)
918
+
919
+ # Chat input
920
+ if not st.session_state.waiting_for_response:
921
+ user_question = st.text_input(
922
+ "Ask about your resume:",
923
+ placeholder="e.g., How can I improve my resume for a data scientist role?",
924
+ key="chat_input"
925
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
926
 
927
+ if st.button("Send") and user_question:
928
+ st.session_state.waiting_for_response = True
929
+ st.session_state.chat_history.append(("user", user_question))
 
 
930
 
931
+ with st.spinner("Getting AI response..."):
932
+ response = analyzer.chatbot.generate_response(
933
+ user_question,
934
+ st.session_state.resume_context
935
+ )
936
+
937
+ st.session_state.chat_history.append(("assistant", response))
938
+ st.session_state.waiting_for_response = False
939
+ st.rerun()
940
+
941
+ # Display chat history
942
+ if st.session_state.chat_history:
943
+ st.subheader("Chat History")
944
+ for role, message in st.session_state.chat_history[-6:]: # Show last 6 messages
945
+ if role == "user":
946
+ st.write(f"**You:** {message}")
947
+ else:
948
+ st.write(f"**Assistant:** {message}")
949
+
950
+ if st.button("Clear Chat History"):
951
+ st.session_state.chat_history = []
952
+ st.rerun()
953
+
954
  except Exception as e:
955
  st.error(f"Error during analysis: {str(e)}")
956
+ st.error("Please check your resume format and try again.")
957
+
958
  else:
959
+ st.error("❌ Could not extract text from the uploaded file. Please check the file format and try again.")
 
 
 
 
 
 
 
 
 
 
 
960
 
961
+ else:
962
+ # Show instructions when no file is uploaded
963
+ st.info("πŸ‘† Please upload your resume to get started!")
964
+
965
+ # Show sample analysis
966
+ with st.expander("πŸ“– What you'll get:", expanded=True):
967
  st.markdown("""
968
+ **Our AI-powered resume analyzer provides:**
969
+
970
+ - **πŸ“Š Comprehensive Scoring**: ATS compatibility and role-specific matching scores
971
+ - **🎯 Skills Analysis**: Technical and soft skills identification
972
+ - **πŸ“ Section Breakdown**: Detailed analysis of each resume section
973
+ - **πŸ” ATS Optimization**: Tips to improve applicant tracking system compatibility
974
+ - **πŸ€– AI Chat Assistant**: Ask questions and get personalized advice
975
+ - **πŸ“„ PDF Report**: Downloadable analysis report
976
+
977
+ **Supported Formats**: PDF, DOCX, TXT
978
  """)
979
 
980
  if __name__ == "__main__":