Spaces:
Running
Running
Update src/app.py
Browse files- src/app.py +246 -188
src/app.py
CHANGED
|
@@ -187,6 +187,7 @@ class ImprovedNLPProcessor:
|
|
| 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,6 +203,7 @@ 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,131 +212,295 @@ class ImprovedChatMemory:
|
|
| 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:
|
| 218 |
def __init__(self):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
self.nlp_processor = ImprovedNLPProcessor()
|
| 220 |
self.memory = ImprovedChatMemory()
|
| 221 |
self.is_loaded = False
|
| 222 |
|
| 223 |
-
#
|
| 224 |
self.template_responses = {
|
| 225 |
'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.",
|
| 226 |
'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.",
|
| 227 |
'skills': "Enhance your skills section by: Organizing technical and soft skills separately, matching skills to job descriptions, providing proficiency levels, and including both hard and soft skills relevant to your target role.",
|
| 228 |
'keywords': "Add relevant keywords by: Studying job descriptions in your field, using industry-specific terms, including both acronyms and full terms, and incorporating them naturally throughout your resume.",
|
| 229 |
-
'format': "Improve resume formatting with: Clear section headings, consistent bullet points, readable fonts, appropriate white space, and a clean, professional layout that's easy to scan."
|
| 230 |
-
'summary': "Write an effective summary by: Starting with your professional title, highlighting 2-3 key achievements, mentioning relevant skills, and keeping it to 3-4 lines maximum.",
|
| 231 |
-
'education': "Optimize your education section by: Listing degree, institution, and graduation year, including relevant coursework for entry-level positions, mentioning GPA if above 3.5, and adding academic achievements.",
|
| 232 |
-
'projects': "Showcase projects effectively by: Describing the problem solved, technologies used, your specific role, quantifiable results or impact, and linking to live demos or repositories when possible.",
|
| 233 |
-
'cover_letter': "Create compelling cover letters by: Addressing specific job requirements, showing knowledge of the company, highlighting relevant achievements, and ending with a strong call to action.",
|
| 234 |
-
'interview': "Prepare for interviews by: Researching the company and role, practicing STAR method responses, preparing questions to ask, and reviewing your resume thoroughly to discuss any point confidently."
|
| 235 |
}
|
| 236 |
|
| 237 |
-
|
| 238 |
-
|
|
|
|
| 239 |
try:
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
except Exception as e:
|
| 246 |
-
st.error(f"Failed to
|
| 247 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
|
| 249 |
def get_template_response(self, user_input: str) -> str:
|
| 250 |
-
"""
|
| 251 |
user_lower = user_input.lower()
|
| 252 |
|
| 253 |
-
#
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
]
|
| 266 |
-
|
| 267 |
-
for keywords, response_key in response_mapping:
|
| 268 |
-
if any(keyword in user_lower for keyword in keywords):
|
| 269 |
-
return self.template_responses[response_key]
|
| 270 |
-
|
| 271 |
-
# General improvement queries
|
| 272 |
-
if any(phrase in user_lower for phrase in ['improve my resume', 'better resume', 'hire me', 'get hired', 'land job']):
|
| 273 |
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."
|
|
|
|
|
|
|
| 274 |
|
| 275 |
-
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
-
|
| 279 |
-
|
| 280 |
|
| 281 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
|
| 283 |
def generate_response(self, user_input: str, resume_context: str = "") -> str:
|
| 284 |
-
"""Generate response
|
| 285 |
if not self.is_loaded:
|
| 286 |
-
return "Please initialize the AI
|
| 287 |
|
| 288 |
-
#
|
| 289 |
template_response = self.get_template_response(user_input)
|
| 290 |
if template_response:
|
| 291 |
self.memory.add_conversation(user_input, template_response)
|
| 292 |
return template_response
|
| 293 |
|
| 294 |
-
#
|
| 295 |
-
|
| 296 |
-
self.
|
| 297 |
-
|
|
|
|
| 298 |
|
| 299 |
-
def
|
| 300 |
-
"""Provide advice based on
|
| 301 |
user_lower = user_input.lower()
|
| 302 |
|
| 303 |
-
#
|
| 304 |
-
if resume_context:
|
| 305 |
-
word_count = len(resume_context.split())
|
| 306 |
-
has_contact = '@' in resume_context or any(char.isdigit() for char in resume_context[:200])
|
| 307 |
-
has_bullets = any(marker in resume_context for marker in ['•', '*', '-', '→'])
|
| 308 |
-
|
| 309 |
-
context_advice = []
|
| 310 |
-
|
| 311 |
-
if word_count < 200:
|
| 312 |
-
context_advice.append("Your resume appears brief - consider expanding with more detailed achievements and responsibilities.")
|
| 313 |
-
elif word_count > 800:
|
| 314 |
-
context_advice.append("Your resume might be too lengthy - focus on the most relevant and impactful information.")
|
| 315 |
-
|
| 316 |
-
if not has_contact:
|
| 317 |
-
context_advice.append("Ensure your contact information (email and phone) is clearly visible at the top.")
|
| 318 |
-
|
| 319 |
-
if not has_bullets:
|
| 320 |
-
context_advice.append("Use bullet points to improve readability and make your achievements easier to scan.")
|
| 321 |
-
|
| 322 |
-
if context_advice:
|
| 323 |
-
return "Based on your resume: " + " ".join(context_advice)
|
| 324 |
-
|
| 325 |
-
# Default comprehensive advice based on query type
|
| 326 |
if any(phrase in user_lower for phrase in ['improve', 'better', 'enhance', 'optimize']):
|
| 327 |
-
return """To improve your resume effectiveness: 1) Tailor
|
| 328 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
elif any(phrase in user_lower for phrase in ['job', 'career', 'position', 'role', 'work']):
|
| 330 |
-
return """For
|
| 331 |
|
| 332 |
-
|
| 333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
else:
|
| 336 |
-
|
|
|
|
|
|
|
| 337 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
@st.cache_resource
|
| 339 |
def download_nltk_data():
|
| 340 |
try:
|
|
@@ -700,114 +866,6 @@ class ResumeAnalyzer:
|
|
| 700 |
buffer.seek(0)
|
| 701 |
return buffer
|
| 702 |
|
| 703 |
-
def create_improved_chat_interface(resume_context: str = ""):
|
| 704 |
-
"""Create improved chat interface with better UI"""
|
| 705 |
-
|
| 706 |
-
st.markdown('<h3 class="section-header">AI Resume Assistant</h3>', unsafe_allow_html=True)
|
| 707 |
-
|
| 708 |
-
# Initialize chatbot
|
| 709 |
-
if 'improved_chatbot' not in st.session_state:
|
| 710 |
-
st.session_state.improved_chatbot = ImprovedCPUChatbot()
|
| 711 |
-
|
| 712 |
-
chatbot = st.session_state.improved_chatbot
|
| 713 |
-
|
| 714 |
-
# Model status and initialization
|
| 715 |
-
col1, col2 = st.columns([3, 1])
|
| 716 |
-
|
| 717 |
-
with col1:
|
| 718 |
-
if chatbot.is_loaded:
|
| 719 |
-
display_alert_box("AI Assistant is ready to help with your resume questions", "success")
|
| 720 |
-
else:
|
| 721 |
-
display_alert_box("Click 'Initialize AI' to start the assistant", "info")
|
| 722 |
-
|
| 723 |
-
with col2:
|
| 724 |
-
if st.button("Initialize AI", type="primary", use_container_width=True):
|
| 725 |
-
with st.spinner("Initializing AI model..."):
|
| 726 |
-
if chatbot.initialize():
|
| 727 |
-
st.success("AI model loaded successfully!")
|
| 728 |
-
st.rerun()
|
| 729 |
-
else:
|
| 730 |
-
st.error("Failed to initialize AI model")
|
| 731 |
-
|
| 732 |
-
if chatbot.is_loaded:
|
| 733 |
-
# Quick questions section
|
| 734 |
-
st.markdown('<h4 class="section-header">Quick Questions</h4>', unsafe_allow_html=True)
|
| 735 |
-
|
| 736 |
-
quick_questions = [
|
| 737 |
-
("Experience Section Help", "How can I improve my experience section?"),
|
| 738 |
-
("ATS Optimization", "How do I make my resume ATS-friendly?"),
|
| 739 |
-
("Keywords & Skills", "What keywords should I include?"),
|
| 740 |
-
("Format & Layout", "How should I format my resume?")
|
| 741 |
-
]
|
| 742 |
-
|
| 743 |
-
cols = st.columns(2)
|
| 744 |
-
for i, (title, question) in enumerate(quick_questions):
|
| 745 |
-
with cols[i % 2]:
|
| 746 |
-
if st.button(title, use_container_width=True):
|
| 747 |
-
st.session_state.quick_question = question
|
| 748 |
-
|
| 749 |
-
# Chat input section
|
| 750 |
-
st.markdown('<h4 class="section-header">Ask a Question</h4>', unsafe_allow_html=True)
|
| 751 |
-
|
| 752 |
-
user_question = st.text_input(
|
| 753 |
-
"Type your resume question here:",
|
| 754 |
-
value=st.session_state.get('quick_question', ''),
|
| 755 |
-
placeholder="How can I improve my resume for better results?",
|
| 756 |
-
key="improved_chat_input"
|
| 757 |
-
)
|
| 758 |
-
|
| 759 |
-
# Action buttons
|
| 760 |
-
col1, col2, col3 = st.columns([1, 1, 2])
|
| 761 |
-
with col1:
|
| 762 |
-
send_clicked = st.button("Send Question", type="primary", use_container_width=True)
|
| 763 |
-
with col2:
|
| 764 |
-
if st.button("Clear Chat", use_container_width=True):
|
| 765 |
-
st.session_state.improved_chat_history = []
|
| 766 |
-
if 'quick_question' in st.session_state:
|
| 767 |
-
del st.session_state.quick_question
|
| 768 |
-
st.rerun()
|
| 769 |
-
|
| 770 |
-
# Generate response
|
| 771 |
-
if send_clicked and user_question.strip():
|
| 772 |
-
with st.spinner("Generating personalized advice..."):
|
| 773 |
-
response = chatbot.generate_response(user_question, resume_context)
|
| 774 |
-
if 'quick_question' in st.session_state:
|
| 775 |
-
del st.session_state.quick_question
|
| 776 |
-
st.rerun()
|
| 777 |
-
|
| 778 |
-
# Display conversation history
|
| 779 |
-
if st.session_state.improved_chat_history:
|
| 780 |
-
st.markdown('<h4 class="section-header">Conversation History</h4>', unsafe_allow_html=True)
|
| 781 |
-
|
| 782 |
-
for conv in reversed(st.session_state.improved_chat_history[-3:]):
|
| 783 |
-
with st.container():
|
| 784 |
-
st.markdown(f"**Question:** {conv['user']}")
|
| 785 |
-
st.markdown(f"**Answer:** {conv['bot']}")
|
| 786 |
-
st.caption(f"Asked at: {conv['timestamp']}")
|
| 787 |
-
st.divider()
|
| 788 |
-
|
| 789 |
-
else:
|
| 790 |
-
# Information about the AI assistant
|
| 791 |
-
st.markdown('<h4 class="section-header">About the AI Assistant</h4>', unsafe_allow_html=True)
|
| 792 |
-
|
| 793 |
-
with st.expander("Features and Capabilities", expanded=True):
|
| 794 |
-
st.markdown("""
|
| 795 |
-
**The AI Resume Assistant provides:**
|
| 796 |
-
|
| 797 |
-
**Instant Expert Advice** - Get immediate answers to common resume questions
|
| 798 |
-
|
| 799 |
-
**Personalized Recommendations** - Tailored advice based on your specific resume content
|
| 800 |
-
|
| 801 |
-
**Quick Response Options** - Pre-built answers for the most frequently asked questions
|
| 802 |
-
|
| 803 |
-
**Conversation Memory** - The assistant remembers your previous questions in the current session
|
| 804 |
-
|
| 805 |
-
**Model Information:**
|
| 806 |
-
- Uses DistilGPT2 with optimized parameters for resume advice
|
| 807 |
-
- Runs locally on CPU for privacy and speed
|
| 808 |
-
- Enhanced with expert knowledge templates for common scenarios
|
| 809 |
-
""")
|
| 810 |
-
|
| 811 |
def main():
|
| 812 |
st.set_page_config(
|
| 813 |
page_title="Professional Resume Analyzer",
|
|
|
|
| 187 |
(token.isalpha() or token in resume_keywords)):
|
| 188 |
filtered_tokens.append(token)
|
| 189 |
|
| 190 |
+
# Return only the most relevant terms
|
| 191 |
return ' '.join(filtered_tokens[:max_terms])
|
| 192 |
|
| 193 |
class ImprovedChatMemory:
|
|
|
|
| 203 |
}
|
| 204 |
st.session_state.improved_chat_history.append(conversation)
|
| 205 |
|
| 206 |
+
# Keep only last 6 conversations
|
| 207 |
if len(st.session_state.improved_chat_history) > 6:
|
| 208 |
st.session_state.improved_chat_history = st.session_state.improved_chat_history[-6:]
|
| 209 |
|
|
|
|
| 212 |
if not st.session_state.improved_chat_history:
|
| 213 |
return ""
|
| 214 |
|
| 215 |
+
# Only use the last conversation for context
|
| 216 |
last_conv = st.session_state.improved_chat_history[-1]
|
| 217 |
+
last_topic = last_conv['user'][:30] # First 30 chars only
|
| 218 |
return f"Previously discussed: {last_topic}"
|
| 219 |
|
| 220 |
class ImprovedCPUChatbot:
|
| 221 |
def __init__(self):
|
| 222 |
+
self.model_name = "distilgpt2"
|
| 223 |
+
self.model = None
|
| 224 |
+
self.tokenizer = None
|
| 225 |
+
self.pipeline = None
|
| 226 |
self.nlp_processor = ImprovedNLPProcessor()
|
| 227 |
self.memory = ImprovedChatMemory()
|
| 228 |
self.is_loaded = False
|
| 229 |
|
| 230 |
+
# Predefined responses for common resume questions
|
| 231 |
self.template_responses = {
|
| 232 |
'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.",
|
| 233 |
'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.",
|
| 234 |
'skills': "Enhance your skills section by: Organizing technical and soft skills separately, matching skills to job descriptions, providing proficiency levels, and including both hard and soft skills relevant to your target role.",
|
| 235 |
'keywords': "Add relevant keywords by: Studying job descriptions in your field, using industry-specific terms, including both acronyms and full terms, and incorporating them naturally throughout your resume.",
|
| 236 |
+
'format': "Improve resume formatting with: Clear section headings, consistent bullet points, readable fonts, appropriate white space, and a clean, professional layout that's easy to scan."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
}
|
| 238 |
|
| 239 |
+
@st.cache_resource
|
| 240 |
+
def load_model(_self):
|
| 241 |
+
"""Load the model with better configuration"""
|
| 242 |
try:
|
| 243 |
+
with st.spinner("Loading AI model (first time may take 2-3 minutes)..."):
|
| 244 |
+
tokenizer = AutoTokenizer.from_pretrained(_self.model_name)
|
| 245 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 246 |
+
|
| 247 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 248 |
+
_self.model_name,
|
| 249 |
+
torch_dtype=torch.float32,
|
| 250 |
+
low_cpu_mem_usage=True
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
# Create pipeline with better parameters
|
| 254 |
+
text_generator = pipeline(
|
| 255 |
+
"text-generation",
|
| 256 |
+
model=model,
|
| 257 |
+
tokenizer=tokenizer,
|
| 258 |
+
device=-1, # CPU only
|
| 259 |
+
max_new_tokens=50, # Reduced for better quality
|
| 260 |
+
do_sample=True,
|
| 261 |
+
temperature=0.8,
|
| 262 |
+
top_p=0.85,
|
| 263 |
+
top_k=50,
|
| 264 |
+
repetition_penalty=1.2, # Reduce repetition
|
| 265 |
+
pad_token_id=tokenizer.eos_token_id,
|
| 266 |
+
no_repeat_ngram_size=3 # Prevent 3-gram repetition
|
| 267 |
+
)
|
| 268 |
+
|
| 269 |
+
return model, tokenizer, text_generator
|
| 270 |
except Exception as e:
|
| 271 |
+
st.error(f"Failed to load model: {str(e)}")
|
| 272 |
+
return None, None, None
|
| 273 |
+
|
| 274 |
+
def initialize(self):
|
| 275 |
+
"""Initialize the chatbot"""
|
| 276 |
+
if not self.is_loaded:
|
| 277 |
+
result = self.load_model()
|
| 278 |
+
if result[0] is not None:
|
| 279 |
+
self.model, self.tokenizer, self.pipeline = result
|
| 280 |
+
self.is_loaded = True
|
| 281 |
+
st.success("AI model loaded successfully!")
|
| 282 |
+
return True
|
| 283 |
+
else:
|
| 284 |
+
return False
|
| 285 |
+
return True
|
| 286 |
|
| 287 |
def get_template_response(self, user_input: str) -> str:
|
| 288 |
+
"""Check if we can use a template response for common questions"""
|
| 289 |
user_lower = user_input.lower()
|
| 290 |
|
| 291 |
+
# Check for common patterns
|
| 292 |
+
if any(word in user_lower for word in ['experience', 'work history', 'job history']):
|
| 293 |
+
return self.template_responses['experience']
|
| 294 |
+
elif any(word in user_lower for word in ['ats', 'applicant tracking', 'ats-friendly']):
|
| 295 |
+
return self.template_responses['ats']
|
| 296 |
+
elif any(word in user_lower for word in ['skills', 'technical skills', 'abilities']):
|
| 297 |
+
return self.template_responses['skills']
|
| 298 |
+
elif any(word in user_lower for word in ['keywords', 'keyword', 'terms']):
|
| 299 |
+
return self.template_responses['keywords']
|
| 300 |
+
elif any(word in user_lower for word in ['format', 'formatting', 'layout', 'design']):
|
| 301 |
+
return self.template_responses['format']
|
| 302 |
+
# Add general improvement patterns
|
| 303 |
+
elif any(phrase in user_lower for phrase in ['improve my resume', 'better resume', 'hire me', 'get hired', 'land job']):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
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."
|
| 305 |
+
elif any(word in user_lower for word in ['help', 'advice', 'tips', 'suggestions']):
|
| 306 |
+
return "Key resume tips: Match your resume to each job application. Use metrics to show your impact. Include both technical and soft skills. Write a compelling summary. Use reverse chronological order. Keep formatting clean and simple."
|
| 307 |
|
| 308 |
+
return None
|
| 309 |
+
|
| 310 |
+
def create_simple_prompt(self, user_input: str, resume_context: str = "") -> str:
|
| 311 |
+
"""Create a very simple, clear prompt"""
|
| 312 |
+
# Try template response first
|
| 313 |
+
template_response = self.get_template_response(user_input)
|
| 314 |
+
if template_response:
|
| 315 |
+
return template_response
|
| 316 |
|
| 317 |
+
# Extract key terms
|
| 318 |
+
key_terms = self.nlp_processor.extract_key_terms(user_input)
|
| 319 |
|
| 320 |
+
# Create simple prompt
|
| 321 |
+
if resume_context:
|
| 322 |
+
context_snippet = resume_context[:100].replace('\n', ' ')
|
| 323 |
+
prompt = f"Resume help: {context_snippet}\nQuestion: {user_input}\nAdvice:"
|
| 324 |
+
else:
|
| 325 |
+
prompt = f"Resume question: {user_input}\nHelpful advice:"
|
| 326 |
+
|
| 327 |
+
return prompt
|
| 328 |
|
| 329 |
def generate_response(self, user_input: str, resume_context: str = "") -> str:
|
| 330 |
+
"""Generate response with better quality control and timeout handling"""
|
| 331 |
if not self.is_loaded:
|
| 332 |
+
return "Please initialize the AI model first by clicking 'Initialize AI'."
|
| 333 |
|
| 334 |
+
# Check for template response first (this should catch most questions)
|
| 335 |
template_response = self.get_template_response(user_input)
|
| 336 |
if template_response:
|
| 337 |
self.memory.add_conversation(user_input, template_response)
|
| 338 |
return template_response
|
| 339 |
|
| 340 |
+
# For non-template questions, provide a general helpful response instead of using the model
|
| 341 |
+
# This avoids the generation loops and stuck behavior
|
| 342 |
+
general_response = self.get_comprehensive_advice(user_input)
|
| 343 |
+
self.memory.add_conversation(user_input, general_response)
|
| 344 |
+
return general_response
|
| 345 |
|
| 346 |
+
def get_comprehensive_advice(self, user_input: str) -> str:
|
| 347 |
+
"""Provide comprehensive advice based on user input patterns"""
|
| 348 |
user_lower = user_input.lower()
|
| 349 |
|
| 350 |
+
# Comprehensive resume improvement advice
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
if any(phrase in user_lower for phrase in ['improve', 'better', 'enhance', 'optimize']):
|
| 352 |
+
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."""
|
| 353 |
|
| 354 |
+
# HR/hiring focused advice
|
| 355 |
+
elif any(phrase in user_lower for phrase in ['hr', 'hire', 'hiring', 'recruiter', 'employer']):
|
| 356 |
+
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."""
|
| 357 |
+
|
| 358 |
+
# Job search and career advice
|
| 359 |
elif any(phrase in user_lower for phrase in ['job', 'career', 'position', 'role', 'work']):
|
| 360 |
+
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."""
|
| 361 |
|
| 362 |
+
# General help
|
| 363 |
+
else:
|
| 364 |
+
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."""
|
| 365 |
+
|
| 366 |
+
def get_general_advice(self, user_input: str) -> str:
|
| 367 |
+
"""Fallback advice for when model fails"""
|
| 368 |
+
user_lower = user_input.lower()
|
| 369 |
+
if 'experience' in user_lower:
|
| 370 |
+
return "Focus on achievements with numbers, use action verbs, and show results."
|
| 371 |
+
elif 'skill' in user_lower:
|
| 372 |
+
return "List skills that match the job description and organize them by category."
|
| 373 |
+
elif 'ats' in user_lower:
|
| 374 |
+
return "Use standard headings, include keywords, and avoid complex formatting."
|
| 375 |
+
else:
|
| 376 |
+
return "Make sure your resume is clear, relevant to the job, and easy to read."
|
| 377 |
+
|
| 378 |
+
def clean_response_thoroughly(self, response: str, user_input: str) -> str:
|
| 379 |
+
"""Thoroughly clean the generated response"""
|
| 380 |
+
if not response or len(response.strip()) < 5:
|
| 381 |
+
return self.get_general_advice(user_input)
|
| 382 |
+
|
| 383 |
+
# Remove common problematic patterns
|
| 384 |
+
response = re.sub(r'\|[^|]*\|', '', response) # Remove pipe-separated content
|
| 385 |
+
response = re.sub(r'Advice:\s*', '', response) # Remove "Advice:" repetition
|
| 386 |
+
response = re.sub(r'\s+', ' ', response) # Replace multiple spaces
|
| 387 |
+
response = re.sub(r'[.]{2,}', '.', response) # Replace multiple periods
|
| 388 |
+
|
| 389 |
+
# Split into sentences and filter
|
| 390 |
+
sentences = [s.strip() for s in response.split('.') if s.strip()]
|
| 391 |
+
good_sentences = []
|
| 392 |
+
|
| 393 |
+
seen_content = set()
|
| 394 |
+
for sentence in sentences[:2]: # Max 2 sentences
|
| 395 |
+
if (len(sentence) > 15 and
|
| 396 |
+
sentence.lower() not in seen_content and
|
| 397 |
+
not sentence.lower().startswith(('you are', 'i am', 'as a', 'how do')) and
|
| 398 |
+
'advice' not in sentence.lower()):
|
| 399 |
+
|
| 400 |
+
good_sentences.append(sentence)
|
| 401 |
+
seen_content.add(sentence.lower())
|
| 402 |
|
| 403 |
+
if good_sentences:
|
| 404 |
+
response = '. '.join(good_sentences)
|
| 405 |
+
if not response.endswith('.'):
|
| 406 |
+
response += '.'
|
| 407 |
else:
|
| 408 |
+
response = self.get_general_advice(user_input)
|
| 409 |
+
|
| 410 |
+
return response.strip()
|
| 411 |
|
| 412 |
+
def create_improved_chat_interface(resume_context: str = ""):
|
| 413 |
+
"""Create improved chat interface"""
|
| 414 |
+
|
| 415 |
+
st.header("AI Resume Assistant")
|
| 416 |
+
|
| 417 |
+
# Initialize chatbot
|
| 418 |
+
if 'improved_chatbot' not in st.session_state:
|
| 419 |
+
st.session_state.improved_chatbot = ImprovedCPUChatbot()
|
| 420 |
+
|
| 421 |
+
chatbot = st.session_state.improved_chatbot
|
| 422 |
+
|
| 423 |
+
# Model initialization
|
| 424 |
+
col1, col2 = st.columns([3, 1])
|
| 425 |
+
|
| 426 |
+
with col1:
|
| 427 |
+
st.info("Using DistilGPT2 with improved response quality")
|
| 428 |
+
|
| 429 |
+
with col2:
|
| 430 |
+
if st.button("Initialize AI", type="primary"):
|
| 431 |
+
chatbot.initialize()
|
| 432 |
+
|
| 433 |
+
# Chat interface
|
| 434 |
+
if chatbot.is_loaded:
|
| 435 |
+
st.success("AI Ready")
|
| 436 |
+
|
| 437 |
+
# Quick questions
|
| 438 |
+
st.subheader("Quick Questions")
|
| 439 |
+
col1, col2 = st.columns(2)
|
| 440 |
+
|
| 441 |
+
with col1:
|
| 442 |
+
if st.button("How to improve experience section?"):
|
| 443 |
+
st.session_state.quick_question = "What's wrong with my experience section?"
|
| 444 |
+
|
| 445 |
+
with col2:
|
| 446 |
+
if st.button("Make resume ATS-friendly?"):
|
| 447 |
+
st.session_state.quick_question = "How do I make it more ATS-friendly?"
|
| 448 |
+
|
| 449 |
+
col3, col4 = st.columns(2)
|
| 450 |
+
with col3:
|
| 451 |
+
if st.button("Add better keywords?"):
|
| 452 |
+
st.session_state.quick_question = "What keywords should I add?"
|
| 453 |
+
|
| 454 |
+
with col4:
|
| 455 |
+
if st.button("Improve skills section?"):
|
| 456 |
+
st.session_state.quick_question = "How can I improve my skills section?"
|
| 457 |
+
|
| 458 |
+
# Chat input
|
| 459 |
+
user_question = st.text_input(
|
| 460 |
+
"Ask about your resume:",
|
| 461 |
+
value=st.session_state.get('quick_question', ''),
|
| 462 |
+
placeholder="How can I improve my resume?",
|
| 463 |
+
key="improved_chat_input"
|
| 464 |
+
)
|
| 465 |
+
|
| 466 |
+
# Send button and clear
|
| 467 |
+
col1, col2 = st.columns([1, 3])
|
| 468 |
+
with col1:
|
| 469 |
+
send_clicked = st.button("Send", type="primary")
|
| 470 |
+
with col2:
|
| 471 |
+
if st.button("Clear Chat"):
|
| 472 |
+
st.session_state.improved_chat_history = []
|
| 473 |
+
if 'quick_question' in st.session_state:
|
| 474 |
+
del st.session_state.quick_question
|
| 475 |
+
st.experimental_rerun()
|
| 476 |
+
|
| 477 |
+
# Generate response
|
| 478 |
+
if send_clicked and user_question.strip():
|
| 479 |
+
with st.spinner("Generating advice..."):
|
| 480 |
+
response = chatbot.generate_response(user_question, resume_context)
|
| 481 |
+
if 'quick_question' in st.session_state:
|
| 482 |
+
del st.session_state.quick_question
|
| 483 |
+
st.experimental_rerun()
|
| 484 |
+
|
| 485 |
+
# Display chat history
|
| 486 |
+
if st.session_state.improved_chat_history:
|
| 487 |
+
st.subheader("💬 Conversation")
|
| 488 |
+
|
| 489 |
+
for conv in reversed(st.session_state.improved_chat_history[-3:]): # Show last 3
|
| 490 |
+
st.markdown(f"**You:** {conv['user']}")
|
| 491 |
+
st.markdown(f"**AI:** {conv['bot']}")
|
| 492 |
+
st.caption(f"Time: {conv['timestamp']}")
|
| 493 |
+
st.divider()
|
| 494 |
+
|
| 495 |
+
else:
|
| 496 |
+
st.warning("Click 'Initialize AI' to start chatting")
|
| 497 |
+
|
| 498 |
+
with st.expander("Improved Features"):
|
| 499 |
+
st.markdown("""
|
| 500 |
+
**Model**: DistilGPT2 with enhanced parameters
|
| 501 |
+
**Response time**: 1-3 seconds
|
| 502 |
+
**Quality**: Significantly improved over basic version
|
| 503 |
+
""")
|
| 504 |
@st.cache_resource
|
| 505 |
def download_nltk_data():
|
| 506 |
try:
|
|
|
|
| 866 |
buffer.seek(0)
|
| 867 |
return buffer
|
| 868 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 869 |
def main():
|
| 870 |
st.set_page_config(
|
| 871 |
page_title="Professional Resume Analyzer",
|