Abdelrahmann121 commited on
Commit
6da8668
Β·
verified Β·
1 Parent(s): 0e14cd0

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +473 -0
app.py ADDED
@@ -0,0 +1,473 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datasets import load_dataset
2
+ from transformers import pipeline, AutoTokenizer, AutoModelForSequenceClassification
3
+ from sentence_transformers import SentenceTransformer
4
+ import numpy as np
5
+ import random
6
+ import faiss
7
+ import json
8
+ import logging
9
+ import re
10
+ import streamlit as st
11
+ from datetime import datetime
12
+ import os
13
+
14
+ # Set up logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # ============================
19
+ # DATA PREPARATION
20
+ # ============================
21
+
22
+
23
+ def prepare_dataset():
24
+ """Load and prepare the emotion dataset"""
25
+ print("πŸ“Š Loading emotion dataset...")
26
+
27
+ # Load the dataset
28
+ ds = load_dataset("cardiffnlp/tweet_eval", "emotion")
29
+
30
+ # Define emotion labels (matching the dataset)
31
+ emotion_labels = ["anger", "joy", "optimism", "sadness"]
32
+
33
+ def clean_text(text):
34
+ """Clean and preprocess text"""
35
+ text = text.lower()
36
+ text = re.sub(r"http\S+", "", text) # remove URLs
37
+ text = re.sub(r"[^\w\s]", "", text) # remove special characters
38
+ text = re.sub(r"\d+", "", text) # remove numbers
39
+ text = re.sub(r"\s+", " ", text) # normalize whitespace
40
+ return text.strip()
41
+
42
+ # Sample and prepare training data
43
+ train_data = ds['train']
44
+ train_sample = random.sample(list(train_data), min(1000, len(train_data)))
45
+
46
+ # Convert to RAG format
47
+ rag_json = []
48
+ for row in train_sample:
49
+ cleaned_text = clean_text(row['text'])
50
+ if len(cleaned_text) > 10: # Filter out very short texts
51
+ rag_json.append({
52
+ "text": cleaned_text,
53
+ "emotion": emotion_labels[row['label']],
54
+ "original_text": row['text']
55
+ })
56
+
57
+ print(f"Dataset prepared with {len(rag_json)} samples")
58
+ return rag_json
59
+
60
+ # ============================
61
+ # EMOTION DETECTION MODEL
62
+ # ============================
63
+
64
+ class EmotionDetector:
65
+ def __init__(self):
66
+ self.model_name = "bhadresh-savani/distilbert-base-uncased-emotion"
67
+
68
+ try:
69
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
70
+ self.model = AutoModelForSequenceClassification.from_pretrained(self.model_name)
71
+ self.classifier = pipeline(
72
+ "text-classification",
73
+ model=self.model,
74
+ tokenizer=self.tokenizer,
75
+ return_all_scores=False
76
+ )
77
+ except Exception as e:
78
+ st.error(f"❌ Error loading emotion model: {e}")
79
+ raise
80
+
81
+ def detect_emotion(self, text):
82
+ """Detect emotion from text"""
83
+ try:
84
+ result = self.classifier(text)
85
+ emotion = result[0]['label'].lower()
86
+ confidence = result[0]['score']
87
+
88
+ # Map model outputs to our emotion categories
89
+ emotion_mapping = {
90
+ 'anger': 'anger',
91
+ 'joy': 'joy',
92
+ 'love': 'joy',
93
+ 'happiness': 'joy',
94
+ 'sadness': 'sadness',
95
+ 'fear': 'sadness',
96
+ 'surprise': 'optimism',
97
+ 'optimism': 'optimism'
98
+ }
99
+
100
+ mapped_emotion = emotion_mapping.get(emotion, 'optimism')
101
+ return mapped_emotion, confidence
102
+
103
+ except Exception as e:
104
+ logger.error(f"Error in emotion detection: {e}")
105
+ return 'optimism', 0.5
106
+
107
+ # ============================
108
+ # RAG SYSTEM WITH FAISS
109
+ # ============================
110
+
111
+ class RAGSystem:
112
+ def __init__(self, rag_data):
113
+ self.rag_data = rag_data
114
+ self.texts = [entry['text'] for entry in rag_data]
115
+
116
+ # Initialize embedding model
117
+ self.embed_model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')
118
+
119
+ # Create embeddings
120
+ self.embeddings = self.embed_model.encode(
121
+ self.texts,
122
+ convert_to_numpy=True,
123
+ show_progress_bar=False
124
+ )
125
+
126
+ # Create FAISS index
127
+ dimension = self.embeddings.shape[1]
128
+ self.index = faiss.IndexFlatL2(dimension)
129
+ self.index.add(self.embeddings)
130
+
131
+ def retrieve_templates(self, user_input, detected_emotion, top_k=3):
132
+ """Retrieve relevant templates based on emotion and similarity"""
133
+
134
+ # Filter by emotion first
135
+ emotion_filtered_indices = [
136
+ i for i, entry in enumerate(self.rag_data)
137
+ if entry['emotion'] == detected_emotion
138
+ ]
139
+
140
+ if not emotion_filtered_indices:
141
+ emotion_filtered_indices = list(range(len(self.rag_data)))
142
+
143
+ # Get filtered embeddings
144
+ filtered_embeddings = self.embeddings[emotion_filtered_indices]
145
+ filtered_texts = [self.texts[i] for i in emotion_filtered_indices]
146
+
147
+ # Create temporary index for filtered data
148
+ temp_index = faiss.IndexFlatL2(filtered_embeddings.shape[1])
149
+ temp_index.add(filtered_embeddings)
150
+
151
+ # Search for similar templates
152
+ user_embedding = self.embed_model.encode([user_input], convert_to_numpy=True)
153
+ distances, indices = temp_index.search(
154
+ user_embedding,
155
+ min(top_k, len(filtered_texts))
156
+ )
157
+
158
+ # Get top templates
159
+ top_templates = [filtered_texts[i] for i in indices[0]]
160
+
161
+ return top_templates
162
+
163
+ # ============================
164
+ # RESPONSE GENERATOR
165
+ # ============================
166
+
167
+ class ResponseGenerator:
168
+ def __init__(self, emotion_detector, rag_system):
169
+ self.emotion_detector = emotion_detector
170
+ self.rag_system = rag_system
171
+
172
+ # Empathetic response templates by emotion
173
+ self.response_templates = {
174
+ 'anger': [
175
+ "I can understand why you're feeling frustrated. It's completely valid to feel this way.",
176
+ "Your anger is understandable. Sometimes situations can be really challenging.",
177
+ "I hear that you're upset, and that's okay. These feelings are important."
178
+ ],
179
+ 'sadness': [
180
+ "I'm sorry you're going through a difficult time. Your feelings are valid.",
181
+ "It sounds like you're dealing with something really tough right now.",
182
+ "I can sense your sadness, and I want you to know that it's okay to feel this way."
183
+ ],
184
+ 'joy': [
185
+ "I'm so happy to hear about your positive experience! That's wonderful.",
186
+ "Your joy is contagious! It's great to hear such positive news.",
187
+ "I love hearing about things that make you happy. That sounds amazing!"
188
+ ],
189
+ 'optimism': [
190
+ "Your positive outlook is inspiring. That's a great way to look at things.",
191
+ "I appreciate your hopeful perspective. That's really encouraging.",
192
+ "It's wonderful to hear your optimistic thoughts. Keep that positive energy!"
193
+ ]
194
+ }
195
+
196
+ def generate_response(self, user_input, top_k=3):
197
+ """Generate empathetic response using RAG and few-shot prompting"""
198
+
199
+ try:
200
+ # Step 1: Detect emotion
201
+ detected_emotion, confidence = self.emotion_detector.detect_emotion(user_input)
202
+
203
+ # Step 2: Retrieve relevant templates
204
+ templates = self.rag_system.retrieve_templates(
205
+ user_input, detected_emotion, top_k=top_k
206
+ )
207
+
208
+ # Step 3: Create response using templates and emotion
209
+ base_responses = self.response_templates.get(
210
+ detected_emotion,
211
+ self.response_templates['optimism']
212
+ )
213
+
214
+ # Combine base response with context from templates
215
+ selected_base = random.choice(base_responses)
216
+
217
+ # Create contextual response
218
+ if templates:
219
+ context_template = random.choice(templates)
220
+ # Enhanced response generation
221
+ response = f"{selected_base} I can relate to what you're sharing - {context_template[:80]}. Remember that your feelings are important and valid."
222
+ else:
223
+ response = selected_base
224
+
225
+ # Add disclaimer
226
+ disclaimer = "\n\n⚠️ This is an automated response. For serious emotional concerns, please consult a mental health professional."
227
+
228
+ return response + disclaimer, detected_emotion, confidence
229
+
230
+ except Exception as e:
231
+ error_msg = f"I apologize, but I encountered an error: {str(e)}"
232
+ disclaimer = "\n\n⚠️ This is an automated response. Please consult a professional if needed."
233
+ return error_msg + disclaimer, 'neutral', 0.0
234
+
235
+ # ============================
236
+ # STREAMLIT APP
237
+ # ============================
238
+
239
+ def main():
240
+ # Page config
241
+ st.set_page_config(
242
+ page_title="Empathetic Chatbot",
243
+ page_icon="πŸ’¬",
244
+ layout="wide",
245
+ initial_sidebar_state="expanded"
246
+ )
247
+
248
+ # Custom CSS
249
+ st.markdown("""
250
+ <style>
251
+ .stApp {
252
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
253
+ }
254
+ .main-header {
255
+ background: rgba(255,255,255,0.1);
256
+ padding: 1rem;
257
+ border-radius: 10px;
258
+ text-align: center;
259
+ margin-bottom: 2rem;
260
+ }
261
+ .chat-message {
262
+ padding: 1rem;
263
+ margin: 0.5rem 0;
264
+ border-radius: 10px;
265
+ background: rgba(255,255,255,0.9);
266
+ max-width: 70%; /* limit bubble width */
267
+ }
268
+
269
+ .user-message {
270
+ background: rgba(100, 149, 237, 0.2);
271
+ margin-left: auto; /* push to right */
272
+ margin-right: 1rem; /* spacing from edge */
273
+ text-align: left; /* keep text aligned inside bubble */
274
+ }
275
+
276
+ .bot-message {
277
+ background: rgba(50, 205, 50, 0.1);
278
+ margin-left: 1rem; /* spacing from left edge */
279
+ margin-right: auto; /* push to left */
280
+ text-align: left;
281
+ }
282
+
283
+ .emotion-badge {
284
+ display: inline-block;
285
+ padding: 0.25rem 0.5rem;
286
+ border-radius: 15px;
287
+ font-size: 0.8rem;
288
+ font-weight: bold;
289
+ margin: 0.25rem;
290
+ }
291
+ .anger { background-color: #ffebee; color: #c62828; }
292
+ .sadness { background-color: #e3f2fd; color: #1565c0; }
293
+ .joy { background-color: #f3e5f5; color: #7b1fa2; }
294
+ .optimism { background-color: #e8f5e8; color: #2e7d32; }
295
+ </style>
296
+ """, unsafe_allow_html=True)
297
+
298
+ # Header
299
+ st.markdown("""
300
+ <div class="main-header">
301
+ <h1>πŸ’¬ Emotion-Aware Empathetic Chatbot</h1>
302
+ <p>Your AI companion for emotional support and understanding</p>
303
+ </div>
304
+ """, unsafe_allow_html=True)
305
+
306
+ # Initialize session state
307
+ if "chat_history" not in st.session_state:
308
+ st.session_state.chat_history = []
309
+
310
+ if "initialized" not in st.session_state:
311
+ initialize_chatbot()
312
+
313
+ # Sidebar
314
+ with st.sidebar:
315
+ st.header("πŸŽ›οΈ Controls")
316
+
317
+ # Statistics
318
+ if st.session_state.chat_history:
319
+ emotions = [chat['emotion'] for chat in st.session_state.chat_history if 'emotion' in chat]
320
+ if emotions:
321
+ emotion_counts = {}
322
+ for emotion in emotions:
323
+ emotion_counts[emotion] = emotion_counts.get(emotion, 0) + 1
324
+
325
+ st.subheader("πŸ“Š Emotion Statistics")
326
+ for emotion, count in emotion_counts.items():
327
+ st.markdown(f'<span class="emotion-badge {emotion}">{emotion.title()}: {count}</span>', unsafe_allow_html=True)
328
+
329
+ st.markdown("---")
330
+
331
+ # Test buttons
332
+ if st.button("πŸ§ͺ Test Emotion Detection"):
333
+ test_emotion_detection()
334
+
335
+ if st.button("πŸ—‘οΈ Clear Chat History"):
336
+ st.session_state.chat_history = []
337
+ st.rerun()
338
+
339
+ st.markdown("---")
340
+
341
+ # Sample messages
342
+ st.subheader("πŸ’‘ Try these sample messages:")
343
+ sample_messages = [
344
+ "I'm feeling really happy today!",
345
+ "I'm so frustrated with everything",
346
+ "I feel really sad and alone",
347
+ " I’m really surprised about what happend!"
348
+ ]
349
+
350
+ for msg in sample_messages:
351
+ if st.button(f"πŸ“ {msg[:20]}...", key=f"sample_{msg}"):
352
+ process_message(msg)
353
+
354
+ # Main chat interface
355
+ col1, col2 = st.columns([3, 1])
356
+
357
+ with col1:
358
+ st.subheader("πŸ’­ Chat Interface")
359
+
360
+ # Display chat history
361
+ chat_container = st.container()
362
+ with chat_container:
363
+ if st.session_state.chat_history:
364
+ for i, chat in enumerate(st.session_state.chat_history[-10:]): # Show last 10, in chronological order
365
+ # User message
366
+ st.markdown(f"""
367
+ <div class="chat-message user-message">
368
+ <strong>πŸ§‘ You ({chat['timestamp']}):</strong><br>
369
+ πŸ’­ {chat['user']}
370
+ </div>
371
+ """, unsafe_allow_html=True)
372
+
373
+ # Bot response with emotion
374
+ emotion_class = chat.get('emotion', 'optimism')
375
+ confidence = chat.get('confidence', 0.0)
376
+
377
+ st.markdown(f"""
378
+ <div class="chat-message bot-message">
379
+ <strong>πŸ€– Bot:</strong>
380
+ <span class="emotion-badge {emotion_class}">
381
+ {emotion_class.title()} ({confidence:.2f})
382
+ </span><br>
383
+ πŸ’ {chat['bot']}
384
+ </div>
385
+ """, unsafe_allow_html=True)
386
+
387
+ st.markdown("---")
388
+
389
+ # User input at the bottom
390
+ user_input = st.text_input(
391
+ "Your message:",
392
+ placeholder="Type how you're feeling...",
393
+ key="user_input",
394
+ help="Share your thoughts and emotions"
395
+ )
396
+
397
+ col_send = st.columns([1])[0]
398
+
399
+ with col_send:
400
+ if st.button("Send πŸ“€", type="primary", use_container_width=True):
401
+ if user_input.strip():
402
+ process_message(user_input)
403
+ st.rerun()
404
+ else:
405
+ st.warning("⚠️ Please enter a message.")
406
+
407
+ with col2:
408
+ st.subheader("ℹ️ About")
409
+ st.info("""
410
+ This chatbot uses:
411
+ - **Emotion Detection**: Identifies your emotional state
412
+ - **RAG System**: Retrieves relevant response templates
413
+ - **Empathetic Responses**: Provides caring, supportive replies
414
+ """)
415
+
416
+ def initialize_chatbot():
417
+ """Initialize the chatbot systems"""
418
+ try:
419
+ with st.spinner("πŸš€ Initializing chatbot systems..."):
420
+ # Prepare dataset
421
+ rag_data = prepare_dataset()
422
+
423
+ # Initialize systems
424
+ emotion_detector = EmotionDetector()
425
+ rag_system = RAGSystem(rag_data)
426
+ response_generator = ResponseGenerator(emotion_detector, rag_system)
427
+
428
+ # Store in session state
429
+ st.session_state.response_generator = response_generator
430
+ st.session_state.initialized = True
431
+
432
+ st.success("βœ… Chatbot initialized successfully!")
433
+
434
+ except Exception as e:
435
+ st.error(f"❌ Initialization failed: {e}")
436
+ st.stop()
437
+
438
+ def process_message(user_input):
439
+ """Process user message and generate response"""
440
+ if hasattr(st.session_state, 'response_generator'):
441
+ with st.spinner("πŸ€” Generating response..."):
442
+ response, emotion, confidence = st.session_state.response_generator.generate_response(user_input)
443
+
444
+ # Add to chat history
445
+ st.session_state.chat_history.append({
446
+ "user": user_input,
447
+ "bot": response,
448
+ "emotion": emotion,
449
+ "confidence": confidence,
450
+ "timestamp": datetime.now().strftime("%H:%M:%S")
451
+ })
452
+
453
+ def test_emotion_detection():
454
+ """Test emotion detection functionality"""
455
+ if hasattr(st.session_state, 'response_generator'):
456
+ test_messages = [
457
+ "I'm so happy today!",
458
+ "I feel terrible and sad",
459
+ "This makes me really angry",
460
+ " I’m really surprised about what happend!"
461
+ ]
462
+
463
+ st.subheader("πŸ§ͺ Emotion Detection Test Results")
464
+
465
+ for msg in test_messages:
466
+ emotion, confidence = st.session_state.response_generator.emotion_detector.detect_emotion(msg)
467
+ st.markdown(f"""
468
+ **Message:** "{msg}"
469
+ **Emotion:** <span class="emotion-badge {emotion}">{emotion.title()} ({confidence:.3f})</span>
470
+ """, unsafe_allow_html=True)
471
+
472
+ if __name__ == "__main__":
473
+ main()