Spaces:
Sleeping
Sleeping
Update src/app.py
Browse files- src/app.py +245 -182
src/app.py
CHANGED
|
@@ -70,8 +70,10 @@ import re
|
|
| 70 |
from datetime import datetime
|
| 71 |
from typing import Dict, List
|
| 72 |
|
| 73 |
-
#
|
| 74 |
-
|
|
|
|
|
|
|
| 75 |
def __init__(self):
|
| 76 |
self.setup_nltk()
|
| 77 |
|
|
@@ -86,32 +88,30 @@ class SimpleNLPProcessor:
|
|
| 86 |
self.stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'with'}
|
| 87 |
self.lemmatizer = None
|
| 88 |
|
| 89 |
-
def
|
| 90 |
-
"""
|
| 91 |
try:
|
| 92 |
tokens = word_tokenize(text.lower())
|
| 93 |
except:
|
| 94 |
tokens = text.lower().split()
|
| 95 |
|
| 96 |
-
#
|
| 97 |
-
|
| 98 |
-
if token not in self.stop_words and len(token) > 2]
|
| 99 |
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
|
| 107 |
-
# Return
|
| 108 |
-
return ' '.join(filtered_tokens[:
|
| 109 |
|
| 110 |
-
|
| 111 |
-
class SimpleChatMemory:
|
| 112 |
def __init__(self):
|
| 113 |
-
if '
|
| 114 |
-
st.session_state.
|
| 115 |
|
| 116 |
def add_conversation(self, user_msg: str, bot_response: str):
|
| 117 |
conversation = {
|
|
@@ -119,62 +119,69 @@ class SimpleChatMemory:
|
|
| 119 |
'bot': bot_response,
|
| 120 |
'timestamp': datetime.now().strftime("%H:%M:%S")
|
| 121 |
}
|
| 122 |
-
st.session_state.
|
| 123 |
|
| 124 |
-
# Keep only last
|
| 125 |
-
if len(st.session_state.
|
| 126 |
-
st.session_state.
|
| 127 |
|
| 128 |
-
def
|
| 129 |
-
"""Get
|
| 130 |
-
if not st.session_state.
|
| 131 |
return ""
|
| 132 |
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
return " | ".join(context_parts) if context_parts else ""
|
| 139 |
|
| 140 |
-
|
| 141 |
-
class SimpleCPUChatbot:
|
| 142 |
def __init__(self):
|
| 143 |
-
self.model_name = "distilgpt2"
|
| 144 |
self.model = None
|
| 145 |
self.tokenizer = None
|
| 146 |
self.pipeline = None
|
| 147 |
-
self.nlp_processor =
|
| 148 |
-
self.memory =
|
| 149 |
self.is_loaded = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
@st.cache_resource
|
| 152 |
def load_model(_self):
|
| 153 |
-
"""Load the model
|
| 154 |
try:
|
| 155 |
with st.spinner("Loading AI model (first time may take 2-3 minutes)..."):
|
| 156 |
-
# Load tokenizer
|
| 157 |
tokenizer = AutoTokenizer.from_pretrained(_self.model_name)
|
| 158 |
tokenizer.pad_token = tokenizer.eos_token
|
| 159 |
|
| 160 |
-
# Load model with CPU optimization
|
| 161 |
model = AutoModelForCausalLM.from_pretrained(
|
| 162 |
_self.model_name,
|
| 163 |
-
torch_dtype=torch.float32,
|
| 164 |
low_cpu_mem_usage=True
|
| 165 |
)
|
| 166 |
|
| 167 |
-
# Create pipeline
|
| 168 |
text_generator = pipeline(
|
| 169 |
"text-generation",
|
| 170 |
model=model,
|
| 171 |
tokenizer=tokenizer,
|
| 172 |
device=-1, # CPU only
|
| 173 |
-
max_new_tokens=
|
| 174 |
do_sample=True,
|
| 175 |
-
temperature=0.
|
| 176 |
-
top_p=0.
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
| 178 |
)
|
| 179 |
|
| 180 |
return model, tokenizer, text_generator
|
|
@@ -192,67 +199,83 @@ class SimpleCPUChatbot:
|
|
| 192 |
st.success("AI model loaded successfully!")
|
| 193 |
return True
|
| 194 |
else:
|
| 195 |
-
st.error("Failed to load AI model")
|
| 196 |
return False
|
| 197 |
return True
|
| 198 |
|
| 199 |
-
def
|
| 200 |
-
"""
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
-
#
|
| 208 |
-
|
| 209 |
-
"You are a professional resume consultant. Give specific, helpful advice."
|
| 210 |
-
]
|
| 211 |
|
| 212 |
-
#
|
| 213 |
if resume_context:
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
if recent_context:
|
| 219 |
-
prompt_parts.append(f"Previous topics: {recent_context}")
|
| 220 |
-
|
| 221 |
-
# Add key terms from current question
|
| 222 |
-
if key_terms:
|
| 223 |
-
prompt_parts.append(f"Focus areas: {key_terms}")
|
| 224 |
-
|
| 225 |
-
# Add the actual question
|
| 226 |
-
prompt_parts.append(f"Question: {user_input}")
|
| 227 |
-
prompt_parts.append("Advice:")
|
| 228 |
|
| 229 |
-
return
|
| 230 |
|
| 231 |
def generate_response(self, user_input: str, resume_context: str = "") -> str:
|
| 232 |
-
"""Generate response
|
| 233 |
if not self.is_loaded:
|
| 234 |
return "Please initialize the AI model first by clicking 'Initialize AI'."
|
| 235 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
try:
|
| 237 |
-
# Create prompt
|
| 238 |
-
prompt = self.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
-
# Generate
|
| 241 |
result = self.pipeline(
|
| 242 |
prompt,
|
| 243 |
-
max_new_tokens=
|
| 244 |
num_return_sequences=1,
|
| 245 |
temperature=0.7,
|
| 246 |
do_sample=True,
|
| 247 |
-
top_p=0.9
|
|
|
|
| 248 |
)
|
| 249 |
|
| 250 |
# Extract and clean response
|
| 251 |
generated_text = result[0]['generated_text']
|
| 252 |
response = generated_text.replace(prompt, "").strip()
|
| 253 |
|
| 254 |
-
# Clean
|
| 255 |
-
response = self.
|
| 256 |
|
| 257 |
# Add to memory
|
| 258 |
self.memory.add_conversation(user_input, response)
|
|
@@ -260,30 +283,157 @@ class SimpleCPUChatbot:
|
|
| 260 |
return response
|
| 261 |
|
| 262 |
except Exception as e:
|
| 263 |
-
|
|
|
|
|
|
|
| 264 |
|
| 265 |
-
def
|
| 266 |
-
"""
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
sentences = [s.strip() for s in response.split('.') if s.strip()]
|
| 272 |
good_sentences = []
|
| 273 |
|
| 274 |
-
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
good_sentences.append(sentence)
|
|
|
|
| 277 |
|
| 278 |
if good_sentences:
|
| 279 |
response = '. '.join(good_sentences)
|
| 280 |
if not response.endswith('.'):
|
| 281 |
response += '.'
|
| 282 |
else:
|
| 283 |
-
|
| 284 |
-
response = "I'd be happy to help with your resume. Could you be more specific about what you need assistance with?"
|
| 285 |
|
| 286 |
-
return response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
# Download NLTK data if not already present
|
| 288 |
@st.cache_resource
|
| 289 |
def download_nltk_data():
|
|
@@ -708,94 +858,7 @@ class ResumeAnalyzer:
|
|
| 708 |
doc.build(story)
|
| 709 |
buffer.seek(0)
|
| 710 |
return buffer
|
| 711 |
-
|
| 712 |
-
def create_simple_chat_interface(resume_context: str = ""):
|
| 713 |
-
"""Create simple chat interface for the resume analyzer"""
|
| 714 |
-
|
| 715 |
-
st.header("π€ AI Resume Assistant")
|
| 716 |
-
|
| 717 |
-
# Initialize chatbot
|
| 718 |
-
if 'simple_chatbot' not in st.session_state:
|
| 719 |
-
st.session_state.simple_chatbot = SimpleCPUChatbot()
|
| 720 |
-
|
| 721 |
-
chatbot = st.session_state.simple_chatbot
|
| 722 |
-
|
| 723 |
-
# Model initialization
|
| 724 |
-
col1, col2 = st.columns([3, 1])
|
| 725 |
-
|
| 726 |
-
with col1:
|
| 727 |
-
st.info("Using DistilGPT2 - Fast CPU-only model (β250MB download)")
|
| 728 |
-
|
| 729 |
-
with col2:
|
| 730 |
-
if st.button("Initialize AI", type="primary"):
|
| 731 |
-
chatbot.initialize()
|
| 732 |
-
|
| 733 |
-
# Chat interface
|
| 734 |
-
if chatbot.is_loaded:
|
| 735 |
-
st.success("β
AI Ready")
|
| 736 |
-
|
| 737 |
-
# Sample questions
|
| 738 |
-
with st.expander("π‘ Try asking"):
|
| 739 |
-
sample_questions = [
|
| 740 |
-
"How can I improve my resume?",
|
| 741 |
-
"What skills should I add?",
|
| 742 |
-
"How do I make it more ATS-friendly?",
|
| 743 |
-
"What's wrong with my experience section?"
|
| 744 |
-
]
|
| 745 |
-
for q in sample_questions:
|
| 746 |
-
if st.button(q, key=f"sample_{hash(q)}"):
|
| 747 |
-
st.session_state.current_question = q
|
| 748 |
|
| 749 |
-
# Chat input
|
| 750 |
-
user_question = st.text_input(
|
| 751 |
-
"Ask about your resume:",
|
| 752 |
-
value=st.session_state.get('current_question', ''),
|
| 753 |
-
placeholder="How can I improve my resume for tech jobs?",
|
| 754 |
-
key="chat_input"
|
| 755 |
-
)
|
| 756 |
-
|
| 757 |
-
# Send button and clear
|
| 758 |
-
col1, col2 = st.columns([1, 3])
|
| 759 |
-
with col1:
|
| 760 |
-
send_clicked = st.button("Send", type="primary")
|
| 761 |
-
with col2:
|
| 762 |
-
if st.button("Clear Chat"):
|
| 763 |
-
st.session_state.chat_history = []
|
| 764 |
-
if 'current_question' in st.session_state:
|
| 765 |
-
del st.session_state.current_question
|
| 766 |
-
st.experimental_rerun()
|
| 767 |
-
|
| 768 |
-
# Generate response
|
| 769 |
-
if send_clicked and user_question.strip():
|
| 770 |
-
with st.spinner("Thinking..."):
|
| 771 |
-
response = chatbot.generate_response(user_question, resume_context)
|
| 772 |
-
if 'current_question' in st.session_state:
|
| 773 |
-
del st.session_state.current_question
|
| 774 |
-
st.experimental_rerun()
|
| 775 |
-
|
| 776 |
-
# Display chat history
|
| 777 |
-
if st.session_state.chat_history:
|
| 778 |
-
st.subheader("π¬ Conversation")
|
| 779 |
-
|
| 780 |
-
for conv in reversed(st.session_state.chat_history[-5:]): # Show last 5
|
| 781 |
-
st.markdown(f"**You:** {conv['user']}")
|
| 782 |
-
st.markdown(f"**AI:** {conv['bot']}")
|
| 783 |
-
st.caption(f"Time: {conv['timestamp']}")
|
| 784 |
-
st.divider()
|
| 785 |
-
|
| 786 |
-
else:
|
| 787 |
-
st.warning("Click 'Initialize AI' to start chatting")
|
| 788 |
-
|
| 789 |
-
with st.expander("βΉοΈ About this AI"):
|
| 790 |
-
st.markdown("""
|
| 791 |
-
**Model**: DistilGPT2 (CPU-optimized)
|
| 792 |
-
**Size**: ~250MB download
|
| 793 |
-
**Speed**: 2-5 seconds per response
|
| 794 |
-
**Memory**: ~1GB RAM usage
|
| 795 |
-
|
| 796 |
-
This model runs entirely on your CPU and provides helpful resume advice.
|
| 797 |
-
First initialization will download the model files.
|
| 798 |
-
""")
|
| 799 |
def main():
|
| 800 |
st.set_page_config(
|
| 801 |
page_title="AI Resume Analyzer with Chatbot",
|
|
@@ -1046,7 +1109,7 @@ def main():
|
|
| 1046 |
# Chat Interface - Properly indented inside the file upload condition
|
| 1047 |
st.markdown("---")
|
| 1048 |
st.header("π¬ Chat with Resume Assistant")
|
| 1049 |
-
|
| 1050 |
|
| 1051 |
except Exception as e:
|
| 1052 |
st.error(f"Error during analysis: {str(e)}")
|
|
|
|
| 70 |
from datetime import datetime
|
| 71 |
from typing import Dict, List
|
| 72 |
|
| 73 |
+
# Set seed for reproducibility
|
| 74 |
+
set_seed(42)
|
| 75 |
+
|
| 76 |
+
class ImprovedNLPProcessor:
|
| 77 |
def __init__(self):
|
| 78 |
self.setup_nltk()
|
| 79 |
|
|
|
|
| 88 |
self.stop_words = {'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'with'}
|
| 89 |
self.lemmatizer = None
|
| 90 |
|
| 91 |
+
def extract_key_terms(self, text: str, max_terms: int = 5) -> str:
|
| 92 |
+
"""Extract key terms without overwhelming the model"""
|
| 93 |
try:
|
| 94 |
tokens = word_tokenize(text.lower())
|
| 95 |
except:
|
| 96 |
tokens = text.lower().split()
|
| 97 |
|
| 98 |
+
# Focus on resume-relevant terms
|
| 99 |
+
resume_keywords = ['resume', 'experience', 'skills', 'education', 'job', 'work', 'ats', 'career']
|
|
|
|
| 100 |
|
| 101 |
+
filtered_tokens = []
|
| 102 |
+
for token in tokens:
|
| 103 |
+
if (len(token) > 2 and
|
| 104 |
+
token not in self.stop_words and
|
| 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:
|
|
|
|
| 112 |
def __init__(self):
|
| 113 |
+
if 'improved_chat_history' not in st.session_state:
|
| 114 |
+
st.session_state.improved_chat_history = []
|
| 115 |
|
| 116 |
def add_conversation(self, user_msg: str, bot_response: str):
|
| 117 |
conversation = {
|
|
|
|
| 119 |
'bot': bot_response,
|
| 120 |
'timestamp': datetime.now().strftime("%H:%M:%S")
|
| 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 |
|
| 128 |
+
def get_simple_context(self) -> str:
|
| 129 |
+
"""Get very simple context to avoid confusing the model"""
|
| 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:
|
|
|
|
| 139 |
def __init__(self):
|
| 140 |
+
self.model_name = "distilgpt2"
|
| 141 |
self.model = None
|
| 142 |
self.tokenizer = None
|
| 143 |
self.pipeline = None
|
| 144 |
+
self.nlp_processor = ImprovedNLPProcessor()
|
| 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.",
|
| 152 |
+
'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.",
|
| 153 |
+
'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.",
|
| 154 |
+
'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."
|
| 155 |
+
}
|
| 156 |
|
| 157 |
@st.cache_resource
|
| 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 |
|
|
|
|
| 165 |
model = AutoModelForCausalLM.from_pretrained(
|
| 166 |
_self.model_name,
|
| 167 |
+
torch_dtype=torch.float32,
|
| 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
|
|
|
|
| 199 |
st.success("AI model loaded successfully!")
|
| 200 |
return True
|
| 201 |
else:
|
|
|
|
| 202 |
return False
|
| 203 |
return True
|
| 204 |
|
| 205 |
+
def get_template_response(self, user_input: str) -> str:
|
| 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']):
|
| 213 |
+
return self.template_responses['ats']
|
| 214 |
+
elif any(word in user_lower for word in ['skills', 'technical skills', 'abilities']):
|
| 215 |
+
return self.template_responses['skills']
|
| 216 |
+
elif any(word in user_lower for word in ['keywords', 'keyword', 'terms']):
|
| 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 |
+
|
| 221 |
+
return None
|
| 222 |
+
|
| 223 |
+
def create_simple_prompt(self, user_input: str, resume_context: str = "") -> str:
|
| 224 |
+
"""Create a very simple, clear prompt"""
|
| 225 |
+
# Try template response first
|
| 226 |
+
template_response = self.get_template_response(user_input)
|
| 227 |
+
if template_response:
|
| 228 |
+
return template_response
|
| 229 |
|
| 230 |
+
# Extract key terms
|
| 231 |
+
key_terms = self.nlp_processor.extract_key_terms(user_input)
|
|
|
|
|
|
|
| 232 |
|
| 233 |
+
# Create simple prompt
|
| 234 |
if resume_context:
|
| 235 |
+
context_snippet = resume_context[:100].replace('\n', ' ')
|
| 236 |
+
prompt = f"Resume help: {context_snippet}\nQuestion: {user_input}\nAdvice:"
|
| 237 |
+
else:
|
| 238 |
+
prompt = f"Resume question: {user_input}\nHelpful advice:"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
+
return prompt
|
| 241 |
|
| 242 |
def generate_response(self, user_input: str, resume_context: str = "") -> str:
|
| 243 |
+
"""Generate response with better quality control"""
|
| 244 |
if not self.is_loaded:
|
| 245 |
return "Please initialize the AI model first by clicking 'Initialize AI'."
|
| 246 |
|
| 247 |
+
# Check for template response first
|
| 248 |
+
template_response = self.get_template_response(user_input)
|
| 249 |
+
if template_response:
|
| 250 |
+
self.memory.add_conversation(user_input, template_response)
|
| 251 |
+
return template_response
|
| 252 |
+
|
| 253 |
try:
|
| 254 |
+
# Create simple prompt
|
| 255 |
+
prompt = self.create_simple_prompt(user_input, resume_context)
|
| 256 |
+
|
| 257 |
+
# If prompt is a template response, return it directly
|
| 258 |
+
if prompt in self.template_responses.values():
|
| 259 |
+
self.memory.add_conversation(user_input, prompt)
|
| 260 |
+
return prompt
|
| 261 |
|
| 262 |
+
# Generate with model
|
| 263 |
result = self.pipeline(
|
| 264 |
prompt,
|
| 265 |
+
max_new_tokens=40,
|
| 266 |
num_return_sequences=1,
|
| 267 |
temperature=0.7,
|
| 268 |
do_sample=True,
|
| 269 |
+
top_p=0.9,
|
| 270 |
+
repetition_penalty=1.3
|
| 271 |
)
|
| 272 |
|
| 273 |
# Extract and clean response
|
| 274 |
generated_text = result[0]['generated_text']
|
| 275 |
response = generated_text.replace(prompt, "").strip()
|
| 276 |
|
| 277 |
+
# Clean the response thoroughly
|
| 278 |
+
response = self.clean_response_thoroughly(response, user_input)
|
| 279 |
|
| 280 |
# Add to memory
|
| 281 |
self.memory.add_conversation(user_input, response)
|
|
|
|
| 283 |
return response
|
| 284 |
|
| 285 |
except Exception as e:
|
| 286 |
+
error_response = f"I encountered an error. Here's some general advice: {self.get_general_advice(user_input)}"
|
| 287 |
+
self.memory.add_conversation(user_input, error_response)
|
| 288 |
+
return error_response
|
| 289 |
|
| 290 |
+
def get_general_advice(self, user_input: str) -> str:
|
| 291 |
+
"""Fallback advice for when model fails"""
|
| 292 |
+
user_lower = user_input.lower()
|
| 293 |
+
if 'experience' in user_lower:
|
| 294 |
+
return "Focus on achievements with numbers, use action verbs, and show results."
|
| 295 |
+
elif 'skill' in user_lower:
|
| 296 |
+
return "List skills that match the job description and organize them by category."
|
| 297 |
+
elif 'ats' in user_lower:
|
| 298 |
+
return "Use standard headings, include keywords, and avoid complex formatting."
|
| 299 |
+
else:
|
| 300 |
+
return "Make sure your resume is clear, relevant to the job, and easy to read."
|
| 301 |
+
|
| 302 |
+
def clean_response_thoroughly(self, response: str, user_input: str) -> str:
|
| 303 |
+
"""Thoroughly clean the generated response"""
|
| 304 |
+
if not response or len(response.strip()) < 5:
|
| 305 |
+
return self.get_general_advice(user_input)
|
| 306 |
+
|
| 307 |
+
# Remove common problematic patterns
|
| 308 |
+
response = re.sub(r'\|[^|]*\|', '', response) # Remove pipe-separated content
|
| 309 |
+
response = re.sub(r'Advice:\s*', '', response) # Remove "Advice:" repetition
|
| 310 |
+
response = re.sub(r'\s+', ' ', response) # Replace multiple spaces
|
| 311 |
+
response = re.sub(r'[.]{2,}', '.', response) # Replace multiple periods
|
| 312 |
+
|
| 313 |
+
# Split into sentences and filter
|
| 314 |
sentences = [s.strip() for s in response.split('.') if s.strip()]
|
| 315 |
good_sentences = []
|
| 316 |
|
| 317 |
+
seen_content = set()
|
| 318 |
+
for sentence in sentences[:2]: # Max 2 sentences
|
| 319 |
+
if (len(sentence) > 15 and
|
| 320 |
+
sentence.lower() not in seen_content and
|
| 321 |
+
not sentence.lower().startswith(('you are', 'i am', 'as a', 'how do')) and
|
| 322 |
+
'advice' not in sentence.lower()):
|
| 323 |
+
|
| 324 |
good_sentences.append(sentence)
|
| 325 |
+
seen_content.add(sentence.lower())
|
| 326 |
|
| 327 |
if good_sentences:
|
| 328 |
response = '. '.join(good_sentences)
|
| 329 |
if not response.endswith('.'):
|
| 330 |
response += '.'
|
| 331 |
else:
|
| 332 |
+
response = self.get_general_advice(user_input)
|
|
|
|
| 333 |
|
| 334 |
+
return response.strip()
|
| 335 |
+
|
| 336 |
+
def create_improved_chat_interface(resume_context: str = ""):
|
| 337 |
+
"""Create improved chat interface"""
|
| 338 |
+
|
| 339 |
+
st.header("π€ AI Resume Assistant")
|
| 340 |
+
|
| 341 |
+
# Initialize chatbot
|
| 342 |
+
if 'improved_chatbot' not in st.session_state:
|
| 343 |
+
st.session_state.improved_chatbot = ImprovedCPUChatbot()
|
| 344 |
+
|
| 345 |
+
chatbot = st.session_state.improved_chatbot
|
| 346 |
+
|
| 347 |
+
# Model initialization
|
| 348 |
+
col1, col2 = st.columns([3, 1])
|
| 349 |
+
|
| 350 |
+
with col1:
|
| 351 |
+
st.info("Using DistilGPT2 with improved response quality")
|
| 352 |
+
|
| 353 |
+
with col2:
|
| 354 |
+
if st.button("Initialize AI", type="primary"):
|
| 355 |
+
chatbot.initialize()
|
| 356 |
+
|
| 357 |
+
# Chat interface
|
| 358 |
+
if chatbot.is_loaded:
|
| 359 |
+
st.success("β
AI Ready")
|
| 360 |
+
|
| 361 |
+
# Quick questions
|
| 362 |
+
st.subheader("Quick Questions")
|
| 363 |
+
col1, col2 = st.columns(2)
|
| 364 |
+
|
| 365 |
+
with col1:
|
| 366 |
+
if st.button("How to improve experience section?"):
|
| 367 |
+
st.session_state.quick_question = "What's wrong with my experience section?"
|
| 368 |
+
|
| 369 |
+
with col2:
|
| 370 |
+
if st.button("Make resume ATS-friendly?"):
|
| 371 |
+
st.session_state.quick_question = "How do I make it more ATS-friendly?"
|
| 372 |
+
|
| 373 |
+
col3, col4 = st.columns(2)
|
| 374 |
+
with col3:
|
| 375 |
+
if st.button("Add better keywords?"):
|
| 376 |
+
st.session_state.quick_question = "What keywords should I add?"
|
| 377 |
+
|
| 378 |
+
with col4:
|
| 379 |
+
if st.button("Improve skills section?"):
|
| 380 |
+
st.session_state.quick_question = "How can I improve my skills section?"
|
| 381 |
+
|
| 382 |
+
# Chat input
|
| 383 |
+
user_question = st.text_input(
|
| 384 |
+
"Ask about your resume:",
|
| 385 |
+
value=st.session_state.get('quick_question', ''),
|
| 386 |
+
placeholder="How can I improve my resume?",
|
| 387 |
+
key="improved_chat_input"
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
# Send button and clear
|
| 391 |
+
col1, col2 = st.columns([1, 3])
|
| 392 |
+
with col1:
|
| 393 |
+
send_clicked = st.button("Send", type="primary")
|
| 394 |
+
with col2:
|
| 395 |
+
if st.button("Clear Chat"):
|
| 396 |
+
st.session_state.improved_chat_history = []
|
| 397 |
+
if 'quick_question' in st.session_state:
|
| 398 |
+
del st.session_state.quick_question
|
| 399 |
+
st.experimental_rerun()
|
| 400 |
+
|
| 401 |
+
# Generate response
|
| 402 |
+
if send_clicked and user_question.strip():
|
| 403 |
+
with st.spinner("Generating advice..."):
|
| 404 |
+
response = chatbot.generate_response(user_question, resume_context)
|
| 405 |
+
if 'quick_question' in st.session_state:
|
| 406 |
+
del st.session_state.quick_question
|
| 407 |
+
st.experimental_rerun()
|
| 408 |
+
|
| 409 |
+
# Display chat history
|
| 410 |
+
if st.session_state.improved_chat_history:
|
| 411 |
+
st.subheader("π¬ Conversation")
|
| 412 |
+
|
| 413 |
+
for conv in reversed(st.session_state.improved_chat_history[-3:]): # Show last 3
|
| 414 |
+
st.markdown(f"**You:** {conv['user']}")
|
| 415 |
+
st.markdown(f"**AI:** {conv['bot']}")
|
| 416 |
+
st.caption(f"Time: {conv['timestamp']}")
|
| 417 |
+
st.divider()
|
| 418 |
+
|
| 419 |
+
else:
|
| 420 |
+
st.warning("Click 'Initialize AI' to start chatting")
|
| 421 |
+
|
| 422 |
+
with st.expander("βΉοΈ Improved Features"):
|
| 423 |
+
st.markdown("""
|
| 424 |
+
**Improvements in this version:**
|
| 425 |
+
|
| 426 |
+
β
**Better response quality** - Reduced repetition and loops
|
| 427 |
+
β
**Template responses** - Instant answers for common questions
|
| 428 |
+
β
**Improved prompting** - Cleaner, more focused prompts
|
| 429 |
+
β
**Response filtering** - Better cleaning of generated text
|
| 430 |
+
β
**Quick questions** - Pre-defined buttons for common queries
|
| 431 |
+
|
| 432 |
+
**Model**: DistilGPT2 with enhanced parameters
|
| 433 |
+
**Response time**: 1-3 seconds
|
| 434 |
+
**Quality**: Significantly improved over basic version
|
| 435 |
+
""")
|
| 436 |
+
|
| 437 |
# Download NLTK data if not already present
|
| 438 |
@st.cache_resource
|
| 439 |
def download_nltk_data():
|
|
|
|
| 858 |
doc.build(story)
|
| 859 |
buffer.seek(0)
|
| 860 |
return buffer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 861 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 862 |
def main():
|
| 863 |
st.set_page_config(
|
| 864 |
page_title="AI Resume Analyzer with Chatbot",
|
|
|
|
| 1109 |
# Chat Interface - Properly indented inside the file upload condition
|
| 1110 |
st.markdown("---")
|
| 1111 |
st.header("π¬ Chat with Resume Assistant")
|
| 1112 |
+
create_improved_chat_interface(st.session_state.get('resume_context', ''))
|
| 1113 |
|
| 1114 |
except Exception as e:
|
| 1115 |
st.error(f"Error during analysis: {str(e)}")
|