suku9 commited on
Commit
f5d7f17
·
verified ·
1 Parent(s): a629e79

Upload 2 files

Browse files
Files changed (2) hide show
  1. app-py.py +416 -0
  2. chatbot-py.py +216 -0
app-py.py ADDED
@@ -0,0 +1,416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import os
4
+ import json
5
+ import re
6
+ import random
7
+ from transformers import (
8
+ AutoTokenizer,
9
+ AutoModelForSequenceClassification,
10
+ AutoModelForCausalLM,
11
+ pipeline,
12
+ )
13
+ import datetime
14
+ import sys
15
+
16
+ # Import classes and functions from the original chatbot file
17
+ from chatbot import (
18
+ ChatbotContext,
19
+ EMOTION_LABELS,
20
+ clean_response_text,
21
+ EMOTION_MAPPING
22
+ )
23
+
24
+ class GradioEmotionChatbot:
25
+ def __init__(self, emotion_model_id, response_model_id=None, confidence_threshold=0.3):
26
+ self.emotion_model_id = emotion_model_id
27
+ self.response_model_id = response_model_id or "mistralai/Mistral-7B-Instruct-v0.2"
28
+ self.confidence_threshold = confidence_threshold
29
+ self.context = ChatbotContext()
30
+ self.initialize_models()
31
+
32
+ def initialize_models(self):
33
+ # Initialize emotion classification model
34
+ print(f"Loading emotion classification model: {self.emotion_model_id}")
35
+ try:
36
+ self.emotion_model = AutoModelForSequenceClassification.from_pretrained(self.emotion_model_id)
37
+ self.emotion_tokenizer = AutoTokenizer.from_pretrained(self.emotion_model_id)
38
+
39
+ self.emotion_classifier = pipeline(
40
+ "text-classification",
41
+ model=self.emotion_model,
42
+ tokenizer=self.emotion_tokenizer,
43
+ top_k=None # Returns scores for all labels
44
+ )
45
+ print("Emotion classification model loaded successfully!")
46
+ except Exception as e:
47
+ print(f"Error loading emotion classification model: {e}")
48
+ # Fallback to a dummy classifier for demo purposes
49
+ self.emotion_classifier = lambda text: [[{"label": "neutral", "score": 1.0}]]
50
+
51
+ # Initialize response generation model (or use fallback)
52
+ print(f"Loading response generation model: {self.response_model_id}")
53
+ try:
54
+ self.response_model = AutoModelForCausalLM.from_pretrained(
55
+ self.response_model_id,
56
+ torch_dtype=torch.float16,
57
+ device_map="auto"
58
+ )
59
+ self.response_tokenizer = AutoTokenizer.from_pretrained(self.response_model_id)
60
+
61
+ self.response_generator = pipeline(
62
+ "text-generation",
63
+ model=self.response_model,
64
+ tokenizer=self.response_tokenizer,
65
+ do_sample=True,
66
+ top_p=0.92,
67
+ top_k=50,
68
+ temperature=0.7,
69
+ max_new_tokens=100
70
+ )
71
+ print("Response generation model loaded successfully!")
72
+ except Exception as e:
73
+ print(f"Using fallback response generation. Reason: {e}")
74
+ self.response_generator = self.fallback_response_generator
75
+
76
+ def fallback_response_generator(self, prompt, **kwargs):
77
+ """Fallback response generator using templates"""
78
+ # Try to extract emotion from the prompt
79
+ emotion_match = re.search(r"emotion: (\w+)", prompt.lower())
80
+ if emotion_match:
81
+ emotion = emotion_match.group(1)
82
+ else:
83
+ emotion = "neutral"
84
+
85
+ # Default user name
86
+ user_name = "friend"
87
+ name_match = re.search(r"Your friend \((.*?)\)", prompt.lower())
88
+ if name_match:
89
+ user_name = name_match.group(1)
90
+
91
+ # Extract user message
92
+ message_match = re.search(r"message: \"(.*?)\"", prompt)
93
+ user_message = message_match.group(1) if message_match else ""
94
+
95
+ # Generate response using fallback method
96
+ response = self.natural_fallback_response(user_message, emotion, user_name)
97
+
98
+ # Format as if coming from the pipeline
99
+ return [{"generated_text": response}]
100
+
101
+ def natural_fallback_response(self, user_message, primary_emotion, user_name):
102
+ """Conversational fallback responses that sound like a supportive friend"""
103
+ # Define emotion categories
104
+ sad_emotions = ["sadness", "disappointment", "grief", "remorse"]
105
+ fear_emotions = ["fear", "nervousness", "anxiety"]
106
+ anger_emotions = ["anger", "annoyance", "disapproval", "disgust"]
107
+ joy_emotions = ["joy", "admiration", "amusement", "excitement", "optimism",
108
+ "gratitude", "pride", "love", "relief"]
109
+
110
+ # Multi-stage response templates - more natural and varied
111
+ if primary_emotion in joy_emotions:
112
+ responses = [
113
+ f"That's awesome, {user_name}! What made you feel that way?",
114
+ f"I'm so glad to hear that! Tell me more about it?",
115
+ f"That's great news! What else is going on with you lately?"
116
+ ]
117
+ elif primary_emotion in sad_emotions:
118
+ responses = [
119
+ f"I'm sorry to hear that, {user_name}. Want to talk about what happened?",
120
+ f"That sounds rough. What's been going on?",
121
+ f"Ugh, that's tough. How are you handling it?"
122
+ ]
123
+ elif primary_emotion in anger_emotions:
124
+ responses = [
125
+ f"That sounds really frustrating. What happened?",
126
+ f"Oh no, that would upset me too. Want to vent about it?",
127
+ f"I can see why you'd be upset about that. What are you thinking of doing?"
128
+ ]
129
+ elif primary_emotion in fear_emotions:
130
+ responses = [
131
+ f"That sounds scary, {user_name}. What's got you worried?",
132
+ f"I can imagine that would be stressful. What's on your mind about it?",
133
+ f"I get feeling anxious about that. What's the biggest concern for you?"
134
+ ]
135
+ else: # neutral emotions
136
+ responses = [
137
+ f"What's been on your mind lately, {user_name}?",
138
+ f"How's everything else going with you?",
139
+ f"Tell me more about what's going on in your life these days."
140
+ ]
141
+
142
+ return random.choice(responses)
143
+
144
+ def classify_text(self, text):
145
+ """Classify text and return emotion data"""
146
+ try:
147
+ results = self.emotion_classifier(text)
148
+
149
+ # Sort emotions by score in descending order
150
+ sorted_emotions = sorted(results[0], key=lambda x: x['score'], reverse=True)
151
+
152
+ # Process emotions above threshold
153
+ detected_emotions = []
154
+ for emotion in sorted_emotions:
155
+ # Map numerical label to emotion name
156
+ try:
157
+ label_id = int(emotion['label'].split('_')[-1]) if '_' in emotion['label'] else int(emotion['label'])
158
+ if 0 <= label_id < len(EMOTION_LABELS):
159
+ emotion_name = EMOTION_LABELS[label_id]
160
+ else:
161
+ emotion_name = emotion['label']
162
+ except (ValueError, IndexError):
163
+ emotion_name = emotion['label']
164
+
165
+ score = emotion['score']
166
+
167
+ if score >= self.confidence_threshold:
168
+ detected_emotions.append({"emotion": emotion_name, "score": score})
169
+
170
+ # If no emotions detected above threshold, add neutral
171
+ if not detected_emotions:
172
+ detected_emotions.append({"emotion": "neutral", "score": 1.0})
173
+
174
+ return detected_emotions
175
+ except Exception as e:
176
+ print(f"Error during classification: {e}")
177
+ # Return neutral as fallback
178
+ return [{"emotion": "neutral", "score": 1.0}]
179
+
180
+ def generate_response(self, user_message, emotion_data):
181
+ """Generate a response based on the user's message and detected emotions"""
182
+ # Get the primary emotion with context awareness
183
+ primary_emotion = emotion_data[0]["emotion"] if emotion_data else "neutral"
184
+ emotion_score = emotion_data[0]["score"] if emotion_data else 1.0
185
+
186
+ # Get recent conversation history for context
187
+ recent_exchanges = self.context.get_recent_messages(6)
188
+ conversation_history = ""
189
+ for msg in recent_exchanges:
190
+ role = "Friend" if msg["role"] == "user" else self.context.bot_name
191
+ conversation_history += f"{role}: {msg['text']}\n"
192
+
193
+ # Check if this is a greeting
194
+ is_greeting = any(greeting in user_message.lower() for greeting in ["hi", "hello", "hey", "greetings"])
195
+ is_question_about_bot = "how are you" in user_message.lower() or any(q in user_message.lower() for q in ["what can you do", "who are you", "what are you", "your purpose"])
196
+
197
+ # Handle special cases first
198
+ if is_greeting:
199
+ if len(self.context.conversation_history) <= 4: # First greeting exchange
200
+ return f"Hi! I'm {self.context.bot_name}. It's nice to meet you. How are you feeling today?"
201
+ else:
202
+ return f"Hey! Good to chat with you again. What's been going on with you?"
203
+
204
+ elif is_question_about_bot:
205
+ return f"I'm doing well, thanks for asking! I'm {self.context.bot_name}, here as a friend to chat whenever you need someone to talk to. What's on your mind today?"
206
+
207
+ # Create a more conversational prompt based on emotion
208
+ system_instruction = f"""You are {self.context.bot_name}, having a natural conversation with your friend. You should respond in a casual, warm way like a supportive friend would - not like a therapist or clinical chatbot.
209
+
210
+ Your friend seems to be feeling {primary_emotion}. In your response:
211
+ 1. Be genuinely empathetic but natural - like how a real friend would respond
212
+ 2. Keep your response short (1-3 sentences) and conversational
213
+ 3. Don't use phrases like "I understand" or "I'm here for you" too much - vary your language
214
+ 4. Use casual language, contractions (don't instead of do not), and occasional sentence fragments
215
+ 5. Don't sound formulaic or overly positive - be authentic
216
+ 6. Keep the same emotional tone throughout your response
217
+ 7. Don't explain what you're doing or add meta-commentary
218
+ 8. DON'T address them by name multiple times or at the end of sentences - it sounds unnatural
219
+ 9. Don't end with "Let me know what you'd prefer" or similar phrases
220
+
221
+ Recent conversation:
222
+ {conversation_history}
223
+
224
+ Your friend's message: "{user_message}"
225
+ Current emotion: {primary_emotion}
226
+
227
+ Respond naturally as a supportive friend (without using their name more than once if at all):"""
228
+
229
+ # Generate the response
230
+ generated = self.response_generator(
231
+ system_instruction,
232
+ max_new_tokens=100,
233
+ do_sample=True,
234
+ temperature=0.8,
235
+ top_p=0.92,
236
+ top_k=50,
237
+ )
238
+
239
+ # Extract the generated text
240
+ if isinstance(generated, list):
241
+ response_text = generated[0].get('generated_text', '')
242
+ else:
243
+ response_text = generated.get('generated_text', '')
244
+
245
+ # Clean up the response
246
+ if "[/INST]" in response_text:
247
+ parts = response_text.split("[/INST]")
248
+ if len(parts) > 1:
249
+ response_text = parts[1].strip()
250
+
251
+ # Remove any model-specific markers
252
+ response_text = response_text.replace("<s>", "").replace("</s>", "")
253
+
254
+ # Remove any internal notes or debugging info that might appear
255
+ if "Note:" in response_text:
256
+ response_text = response_text.split("Note:")[0].strip()
257
+
258
+ # Remove any metadata or system-like text
259
+ response_text = response_text.replace("Assistant:", "").replace(f"{self.context.bot_name}:", "").strip()
260
+
261
+ # Remove any quotation marks surrounding the response
262
+ response_text = response_text.strip('"').strip()
263
+
264
+ # Handle potential model halt mid-sentence
265
+ if response_text.endswith((".", "!", "?")):
266
+ pass # Response ends with proper punctuation
267
+ else:
268
+ # Try to find the last complete sentence
269
+ last_period = max(response_text.rfind("."), response_text.rfind("!"), response_text.rfind("?"))
270
+ if last_period > len(response_text) * 0.5: # If we've got at least half the response
271
+ response_text = response_text[:last_period+1]
272
+
273
+ return clean_response_text(response_text.strip(), self.context.user_name)
274
+
275
+ def process_message(self, user_message, chatbot_history):
276
+ """Process a user message and return the chatbot response"""
277
+ # Initialize context if first message
278
+ if not self.context.introduced and not self.context.conversation_history:
279
+ initial_greeting = f"Hi! I'm {self.context.bot_name}, your friendly emotional support chatbot. Who am I talking to today?"
280
+ self.context.add_message("bot", initial_greeting)
281
+ return chatbot_history + [[None, initial_greeting]]
282
+
283
+ # Handle name collection if this is the first user message
284
+ if not self.context.introduced:
285
+ common_greetings = ["hi", "hey", "hello", "greetings", "howdy", "hiya"]
286
+ words = user_message.strip().split()
287
+ potential_name = None
288
+
289
+ if "i'm" in user_message.lower() or "im" in user_message.lower():
290
+ parts = user_message.lower().replace("i'm", "im").split("im")
291
+ if len(parts) > 1 and parts[1].strip():
292
+ potential_name = parts[1].strip().split()[0].capitalize()
293
+
294
+ elif "my name is" in user_message.lower():
295
+ parts = user_message.lower().split("my name is")
296
+ if len(parts) > 1 and parts[1].strip():
297
+ potential_name = parts[1].strip().split()[0].capitalize()
298
+
299
+ elif len(words) <= 3 and words[0].lower() not in common_greetings:
300
+ potential_name = words[0].capitalize()
301
+
302
+ if potential_name:
303
+ potential_name = ''.join(c for c in potential_name if c.isalnum())
304
+
305
+ if potential_name and len(potential_name) >= 2 and potential_name.lower() not in common_greetings:
306
+ self.context.user_name = potential_name
307
+ greeting_response = f"Nice to meet you, {self.context.user_name}! How are you feeling today?"
308
+ else:
309
+ self.context.user_name = "friend"
310
+ greeting_response = "Nice to meet you! How are you feeling today?"
311
+
312
+ self.context.introduced = True
313
+ self.context.add_message("user", user_message)
314
+ self.context.add_message("bot", greeting_response)
315
+
316
+ return chatbot_history + [[user_message, greeting_response]]
317
+
318
+ # Regular message processing
319
+ emotion_data = self.classify_text(user_message)
320
+ self.context.add_message("user", user_message, emotion_data)
321
+
322
+ # Generate the response
323
+ bot_response = self.generate_response(user_message, emotion_data)
324
+ self.context.add_message("bot", bot_response)
325
+
326
+ # Format detected emotions for display
327
+ emotion_html = "<div style='margin-bottom: 10px;'><b>Detected emotions:</b><ul style='margin: 0; padding-left: 20px;'>"
328
+ for emotion in emotion_data:
329
+ emotion_html += f"<li>{emotion['emotion']}: {emotion['score']:.4f}</li>"
330
+ emotion_html += "</ul></div>"
331
+
332
+ # Return updated chat history with emotion display
333
+ return chatbot_history + [[user_message, f"{emotion_html}{bot_response}"]]
334
+
335
+ def reset_conversation(self):
336
+ """Reset the conversation context"""
337
+ self.context = ChatbotContext()
338
+ return []
339
+
340
+ # Create the Gradio interface
341
+ def create_gradio_interface():
342
+ # Initialize the chatbot with default models
343
+ emotion_model_id = os.environ.get("EMOTION_MODEL_ID", "suku9/emotion_classifier")
344
+ response_model_id = os.environ.get("RESPONSE_MODEL_ID", "mistralai/Mistral-7B-Instruct-v0.2")
345
+
346
+ chatbot = GradioEmotionChatbot(emotion_model_id, response_model_id)
347
+
348
+ # Create the Gradio interface
349
+ with gr.Blocks(css="""
350
+ .gradio-container {max-width: 800px; margin: auto;}
351
+ #chatbot {height: 400px; overflow: auto;}
352
+ #emotion-display {margin-top: 10px; padding: 10px; border: 1px solid #ddd; border-radius: 5px;}
353
+ footer {visibility: hidden}
354
+ """) as demo:
355
+ gr.Markdown("# Emotion-Aware Supportive Chatbot")
356
+ gr.Markdown(f"This chatbot can understand your emotions and provide supportive conversation. It uses an emotion classification model (`{emotion_model_id}`) and a response generation model (`{response_model_id}`).")
357
+
358
+ chatbot_interface = gr.Chatbot(
359
+ elem_id="chatbot",
360
+ show_label=False,
361
+ height=400,
362
+ bubble_full_width=False,
363
+ )
364
+
365
+ with gr.Row():
366
+ user_input = gr.Textbox(
367
+ placeholder="Type your message here...",
368
+ show_label=False,
369
+ container=False,
370
+ scale=9,
371
+ )
372
+ submit_btn = gr.Button("Send", scale=1)
373
+
374
+ with gr.Row():
375
+ clear_btn = gr.Button("New Conversation")
376
+
377
+ # Set up the event handlers
378
+ submit_btn.click(
379
+ chatbot.process_message,
380
+ inputs=[user_input, chatbot_interface],
381
+ outputs=[chatbot_interface],
382
+ ).then(
383
+ lambda: "", # Clear the input box after sending
384
+ None,
385
+ [user_input],
386
+ )
387
+
388
+ user_input.submit(
389
+ chatbot.process_message,
390
+ inputs=[user_input, chatbot_interface],
391
+ outputs=[chatbot_interface],
392
+ ).then(
393
+ lambda: "", # Clear the input box after sending
394
+ None,
395
+ [user_input],
396
+ )
397
+
398
+ clear_btn.click(
399
+ chatbot.reset_conversation,
400
+ inputs=None,
401
+ outputs=[chatbot_interface],
402
+ )
403
+
404
+ # Initialize the conversation with a welcome message
405
+ gr.HTML("""
406
+ <p style="text-align: center; margin-top: 20px; color: #666;">
407
+ Type a message and press Enter or click Send to start chatting.
408
+ The chatbot will analyze your emotions and respond accordingly.
409
+ </p>
410
+ """)
411
+
412
+ return demo
413
+
414
+ if __name__ == "__main__":
415
+ demo = create_gradio_interface()
416
+ demo.launch()
chatbot-py.py ADDED
@@ -0,0 +1,216 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import random
4
+ import datetime
5
+ import re
6
+ import sys
7
+
8
+ # Define emotion label mapping
9
+ EMOTION_LABELS = [
10
+ "admiration", "amusement", "anger", "annoyance", "approval", "caring", "confusion",
11
+ "curiosity", "desire", "disappointment", "disapproval", "disgust", "embarrassment",
12
+ "excitement", "fear", "gratitude", "grief", "joy", "love", "nervousness", "optimism",
13
+ "pride", "realization", "relief", "remorse", "sadness", "surprise", "neutral"
14
+ ]
15
+
16
+ # Map similar emotions to our response categories
17
+ EMOTION_MAPPING = {
18
+ "admiration": "joy",
19
+ "amusement": "joy",
20
+ "anger": "anger",
21
+ "annoyance": "anger",
22
+ "approval": "joy",
23
+ "caring": "joy",
24
+ "confusion": "neutral",
25
+ "curiosity": "neutral",
26
+ "desire": "neutral",
27
+ "disappointment": "sadness",
28
+ "disapproval": "anger",
29
+ "disgust": "disgust",
30
+ "embarrassment": "sadness",
31
+ "excitement": "joy",
32
+ "fear": "fear",
33
+ "gratitude": "joy",
34
+ "grief": "sadness",
35
+ "joy": "joy",
36
+ "love": "joy",
37
+ "nervousness": "fear",
38
+ "optimism": "joy",
39
+ "pride": "joy",
40
+ "realization": "neutral",
41
+ "relief": "joy",
42
+ "remorse": "sadness",
43
+ "sadness": "sadness",
44
+ "surprise": "surprise",
45
+ "neutral": "neutral"
46
+ }
47
+
48
+ class ChatbotContext:
49
+ """Class to maintain conversation context and history"""
50
+ def __init__(self):
51
+ self.conversation_history = []
52
+ self.detected_emotions = []
53
+ self.user_feedback = []
54
+ self.current_session_id = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
55
+ # Track emotional progression for therapeutic conversation flow
56
+ self.conversation_stage = "initial" # initial, middle, advanced
57
+ self.emotion_trajectory = [] # track emotion changes over time
58
+ self.consecutive_positive_count = 0
59
+ self.consecutive_negative_count = 0
60
+ # Add user name tracking
61
+ self.user_name = None
62
+ self.bot_name = "Mira" # Friendly, easy to remember name
63
+ self.introduced = False
64
+
65
+ def add_message(self, role, text, emotions=None):
66
+ """Add a message to the conversation history"""
67
+ timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
68
+ message = {
69
+ "role": role,
70
+ "text": text,
71
+ "timestamp": timestamp
72
+ }
73
+ if emotions and role == "user":
74
+ message["emotions"] = emotions
75
+ self.detected_emotions.append(emotions)
76
+ self._update_emotional_trajectory(emotions)
77
+
78
+ self.conversation_history.append(message)
79
+ return message
80
+
81
+ def _update_emotional_trajectory(self, emotions):
82
+ """Update the emotional trajectory based on newly detected emotions"""
83
+ # Get the primary emotion
84
+ primary_emotion = emotions[0]["emotion"] if emotions else "neutral"
85
+
86
+ # Add to trajectory
87
+ self.emotion_trajectory.append(primary_emotion)
88
+
89
+ # Classify as positive, negative, or neutral
90
+ positive_emotions = ["joy", "admiration", "amusement", "excitement",
91
+ "optimism", "gratitude", "pride", "love", "relief"]
92
+ negative_emotions = ["sadness", "anger", "fear", "disgust", "disappointment",
93
+ "annoyance", "disapproval", "embarrassment", "grief",
94
+ "remorse", "nervousness"]
95
+
96
+ if primary_emotion in positive_emotions:
97
+ self.consecutive_positive_count += 1
98
+ self.consecutive_negative_count = 0
99
+ elif primary_emotion in negative_emotions:
100
+ self.consecutive_negative_count += 1
101
+ self.consecutive_positive_count = 0
102
+ else: # neutral or other
103
+ # Don't reset counters for neutral emotions to maintain progress
104
+ pass
105
+
106
+ # Update conversation stage based on trajectory and message count
107
+ # This now uses conversation history length rather than just emotion trajectory
108
+ msg_count = len(self.conversation_history) // 2 # Count actual exchanges (user/bot pairs)
109
+ if msg_count <= 1: # First real exchange
110
+ self.conversation_stage = "initial"
111
+ elif msg_count <= 3: # First few exchanges
112
+ self.conversation_stage = "middle"
113
+ else: # More established conversation
114
+ self.conversation_stage = "advanced"
115
+
116
+ def get_emotional_state(self):
117
+ """Get the current emotional state of the conversation"""
118
+ if len(self.emotion_trajectory) < 2:
119
+ return "unknown"
120
+
121
+ # Get the last few emotions (with 'neutral' having less weight)
122
+ recent_emotions = self.emotion_trajectory[-3:]
123
+ positive_emotions = ["joy", "admiration", "amusement", "excitement",
124
+ "optimism", "gratitude", "pride", "love", "relief"]
125
+ negative_emotions = ["sadness", "anger", "fear", "disgust", "disappointment",
126
+ "annoyance", "disapproval", "embarrassment", "grief",
127
+ "remorse", "nervousness"]
128
+
129
+ # Count positive and negative emotions
130
+ pos_count = sum(1 for e in recent_emotions if e in positive_emotions)
131
+ neg_count = sum(1 for e in recent_emotions if e in negative_emotions)
132
+
133
+ if self.consecutive_positive_count >= 2:
134
+ return "positive"
135
+ elif self.consecutive_negative_count >= 2:
136
+ return "negative"
137
+ elif pos_count > neg_count:
138
+ return "improving"
139
+ elif neg_count > pos_count:
140
+ return "declining"
141
+ else:
142
+ return "neutral"
143
+
144
+ def add_feedback(self, rating, comments=None):
145
+ """Add user feedback about the chatbot's response"""
146
+ feedback = {
147
+ "rating": rating,
148
+ "comments": comments,
149
+ "timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
150
+ }
151
+ self.user_feedback.append(feedback)
152
+ return feedback
153
+
154
+ def get_recent_messages(self, count=5):
155
+ """Get the most recent messages from the conversation history"""
156
+ return self.conversation_history[-count:] if len(self.conversation_history) >= count else self.conversation_history
157
+
158
+ def save_conversation(self, filepath=None):
159
+ """Save the conversation history to a JSON file"""
160
+ if not filepath:
161
+ os.makedirs("./conversations", exist_ok=True)
162
+ filepath = f"./conversations/conversation_{self.current_session_id}.json"
163
+
164
+ data = {
165
+ "conversation_history": self.conversation_history,
166
+ "user_feedback": self.user_feedback,
167
+ "emotion_trajectory": self.emotion_trajectory,
168
+ "session_id": self.current_session_id,
169
+ "start_time": self.conversation_history[0]["timestamp"] if self.conversation_history else None,
170
+ "end_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
171
+ }
172
+
173
+ with open(filepath, 'w') as f:
174
+ json.dump(data, f, indent=2)
175
+ print(f"Conversation saved to {filepath}")
176
+ return filepath
177
+
178
+ def clean_response_text(response, user_name):
179
+ """Clean up the response text to make it more natural"""
180
+ # Remove repeated name mentions
181
+ if user_name:
182
+ # Replace patterns like "Hey user_name," or "Hi user_name,"
183
+ response = re.sub(r'^(Hey|Hi|Hello)\s+' + re.escape(user_name) + r',?\s+', '', response, flags=re.IGNORECASE)
184
+
185
+ # Replace duplicate name mentions
186
+ pattern = re.escape(user_name) + r',?\s+.*' + re.escape(user_name)
187
+ if re.search(pattern, response, re.IGNORECASE):
188
+ response = re.sub(r',?\s+' + re.escape(user_name) + r'([,.!?])', r'\1', response, flags=re.IGNORECASE)
189
+
190
+ # Remove name at the end of sentences if it appears earlier
191
+ if response.count(user_name) > 1:
192
+ response = re.sub(r',\s+' + re.escape(user_name) + r'([.!?])(\s|$)', r'\1\2', response, flags=re.IGNORECASE)
193
+
194
+ # Remove phrases that feel repetitive or formulaic
195
+ phrases_to_remove = [
196
+ r"let me know what you'd prefer,?\s+",
197
+ r"i'm here to listen,?\s+",
198
+ r"let me know if there's anything else,?\s+",
199
+ r"i'm all ears,?\s+",
200
+ r"i'm here for you,?\s+"
201
+ ]
202
+
203
+ for phrase in phrases_to_remove:
204
+ response = re.sub(phrase, "", response, flags=re.IGNORECASE)
205
+
206
+ # Fix multiple punctuation
207
+ response = re.sub(r'([.!?])\s+\1', r'\1', response)
208
+
209
+ # Fix missing space after punctuation
210
+ response = re.sub(r'([.!?])([A-Za-z])', r'\1 \2', response)
211
+
212
+ # Make sure first letter is capitalized
213
+ if response and len(response) > 0:
214
+ response = response[0].upper() + response[1:]
215
+
216
+ return response.strip()