Pant0x commited on
Commit
a0405e9
·
verified ·
1 Parent(s): 65dc857

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +117 -219
app.py CHANGED
@@ -1,240 +1,138 @@
1
- # ======================= app.py =======================
2
-
3
- import os
4
- import re
5
- import random
6
- import tempfile
7
- import warnings
8
- warnings.filterwarnings("ignore")
9
-
10
- import numpy as np
11
- import pandas as pd
12
- import soundfile as sf
13
- import librosa
14
- import joblib
15
  import gradio as gr
16
- from gtts import gTTS
17
- from sklearn.ensemble import RandomForestClassifier
18
- from sklearn.preprocessing import LabelEncoder
19
-
20
- # ======================= Configuration =======================
21
- CSV_PATH = "deepseek_csv_20251105_09a9e0.csv"
22
- MULTIMODAL_CSV = "multimodal_audio_dataset.csv"
23
- AUDIO_FOLDER = "Dataset"
24
- SAMPLE_RATE = 16000
25
- EMOTIONS_ALLOWED = ["sad", "happy", "anxious", "neutral", "angry"]
26
-
27
- os.makedirs(AUDIO_FOLDER, exist_ok=True)
28
-
29
- # ======================= Audio Feature Extraction =======================
30
-
31
- def extract_audio_features(audio_file):
32
- """
33
- Extract audio features from a .wav file:
34
- - Pitch (fundamental frequency)
35
- - Energy/Intensity
36
- - Tempo (speaking rate)
37
- """
38
- y, sr = librosa.load(audio_file, sr=SAMPLE_RATE)
39
- features = {}
40
-
41
- # Pitch features
42
- pitches, magnitudes = librosa.piptrack(y=y, sr=sr)
43
- pitch_values = [pitches[magnitudes[:, t].argmax(), t]
44
- for t in range(pitches.shape[1])
45
- if magnitudes[:, t].max() > 0]
46
-
47
- features['pitch_mean'] = np.mean(pitch_values) if pitch_values else 0
48
- features['pitch_std'] = np.std(pitch_values) if pitch_values else 0
49
- features['energy_mean'] = np.mean(librosa.feature.rms(y=y)[0])
50
- tempo, _ = librosa.beat.beat_track(y=y, sr=sr)
51
- features['tempo'] = tempo
52
-
53
- return features
54
-
55
- def generate_audio_training_data(n_samples=100):
56
- """
57
- Generate synthetic audio dataset for 5 emotions
58
- """
59
- np.random.seed(42)
60
- emotions = ['sad', 'happy', 'anxious', 'neutral', 'angry']
61
- data = []
62
-
63
- for emo in emotions:
64
- for _ in range(n_samples):
65
- if emo == 'sad':
66
- pitch_mean = np.random.normal(130, 5)
67
- pitch_std = np.random.normal(15, 5)
68
- energy_mean = np.random.uniform(0.015, 0.04)
69
- tempo = np.random.uniform(70, 90)
70
- elif emo == 'happy':
71
- pitch_mean = np.random.normal(220, 10)
72
- pitch_std = np.random.normal(45, 10)
73
- energy_mean = np.random.uniform(0.1, 0.15)
74
- tempo = np.random.uniform(100, 130)
75
- elif emo == 'anxious':
76
- pitch_mean = np.random.normal(180, 10)
77
- pitch_std = np.random.normal(60, 10)
78
- energy_mean = np.random.uniform(0.06, 0.09)
79
- tempo = np.random.uniform(120, 150)
80
- elif emo == 'neutral':
81
- pitch_mean = np.random.normal(160, 10)
82
- pitch_std = np.random.normal(25, 5)
83
- energy_mean = np.random.uniform(0.05, 0.08)
84
- tempo = np.random.uniform(90, 110)
85
- elif emo == 'angry':
86
- pitch_mean = np.random.normal(210, 10)
87
- pitch_std = np.random.normal(50, 10)
88
- energy_mean = np.random.uniform(0.12, 0.18)
89
- tempo = np.random.uniform(120, 160)
90
- data.append([pitch_mean, pitch_std, energy_mean, tempo, emo])
91
-
92
- df = pd.DataFrame(data, columns=['pitch_mean','pitch_std','energy_mean','tempo','true_emotion'])
93
- df.to_csv(MULTIMODAL_CSV, index=False)
94
- print("✓ Multimodal audio dataset saved as 'multimodal_audio_dataset.csv'")
95
- return df
96
-
97
- # ======================= Train Audio RF Model =======================
98
- if not os.path.exists(MULTIMODAL_CSV):
99
- generate_audio_training_data(n_samples=100)
100
-
101
- df_audio = pd.read_csv(MULTIMODAL_CSV)
102
- X_audio = df_audio[['pitch_mean','pitch_std','energy_mean','tempo']]
103
- y_audio = df_audio['true_emotion']
104
-
105
- _audio_rf_le = LabelEncoder()
106
- y_enc = _audio_rf_le.fit_transform(y_audio)
107
-
108
- _audio_rf_model = RandomForestClassifier(n_estimators=200, random_state=42)
109
- _audio_rf_model.fit(X_audio, y_enc)
110
- print("✓ Audio RF model trained from multimodal dataset")
111
-
112
- # ======================= Emotion Prediction =======================
113
- def predict_emotion_from_audiofile(audio_filepath):
114
- try:
115
- features = extract_audio_features(audio_filepath)
116
- X = np.array([[features['pitch_mean'], features['pitch_std'], features['energy_mean'], features['tempo']]])
117
- pred_enc = _audio_rf_model.predict(X)[0]
118
- label = _audio_rf_le.inverse_transform([pred_enc])[0].lower()
119
- return label
120
- except Exception as e:
121
- print(f"Error predicting emotion: {e}")
122
- return random.choice(EMOTIONS_ALLOWED)
123
-
124
- # ======================= Mental Health Text Chat =======================
125
- MENTAL_KEYWORDS = ["depression","anxiety","stress","sad","trauma","therapy","mental","emotion","feel","help"]
126
- OFF_TOPIC = ["song","music","joke","game","food","movie","sport","money","business"]
127
- MENTAL_RESPONSES_EN = [
128
- "I hear that you're going through a difficult time.",
129
- "Thank you for sharing that with me. Your feelings are valid.",
130
- "I'm here to listen and support you. Would you like to talk more?",
131
  ]
132
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  OFF_TOPIC_RESPONSES = [
134
- "Let's focus on emotional well-being. How are you feeling today?",
135
- "I specialize in mental health conversations. Tell me how you're feeling.",
 
 
 
 
 
 
136
  ]
137
 
 
138
  def contains_arabic(text: str) -> bool:
139
  return bool(re.search(r"[\u0600-\u06FF]", text))
140
 
 
141
  def is_mental_health_related(text: str) -> bool:
142
  text_lower = text.lower()
 
 
 
143
  if any(word in text_lower for word in OFF_TOPIC):
144
  return False
 
 
145
  if any(word in text_lower for word in MENTAL_KEYWORDS):
146
  return True
147
- if contains_arabic(text_lower):
 
 
148
  return True
 
 
149
  return False
150
 
151
- def respond(message):
152
- if not message.strip():
153
- return "Please type a message first."
 
 
 
 
 
 
 
 
154
  if not is_mental_health_related(message):
155
- return random.choice(OFF_TOPIC_RESPONSES)
156
- if contains_arabic(message):
157
- return "أنا هنا لدعمك، كيف تشعر اليوم؟"
158
- else:
159
- return random.choice(MENTAL_RESPONSES_EN)
160
-
161
- # ======================= TTS Helper =======================
162
- def make_tts_for_message(text, lang="en"):
163
- try:
164
- tts = gTTS(text, lang=lang)
165
- tmp = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False)
166
- tts.save(tmp.name)
167
- return tmp.name
168
- except Exception as e:
169
- print(f"TTS error: {e}")
170
- return None
171
-
172
- # ======================= Combined Voice Chat =======================
173
- SUPPORT_MESSAGES = {
174
- "sad": "I'm sorry you're feeling sad. I'm here for you.",
175
- "angry": "It's okay to feel angry. I'm here to listen.",
176
- "happy": "I'm glad you're feeling happy. That's good to hear!",
177
- "neutral": "Thanks for sharing. I'm here whenever you need to talk."
178
- }
179
-
180
- def load_audio(path, sr=SAMPLE_RATE):
181
- if not os.path.isfile(path):
182
- raise FileNotFoundError(f"Audio file not found: {path}")
183
- data, orig_sr = sf.read(path, dtype='float32')
184
- if data.ndim > 1:
185
- data = np.mean(data, axis=1)
186
- if orig_sr != sr:
187
- data = librosa.resample(data, orig_sr, sr)
188
- return data
189
-
190
- def voice_chat_combined(audio_path, language):
191
- if not audio_path:
192
- return "No audio received. Please speak.", None
193
-
194
- # Emotion detection
195
- emotion = predict_emotion_from_audiofile(audio_path)
196
- print(f"Detected emotion: {emotion}")
197
-
198
- support = SUPPORT_MESSAGES.get(emotion, "I hear you. I'm here for you.")
199
- tts_lang = "ar" if language.lower().startswith("arab") else "en"
200
- tts_path = make_tts_for_message(support, lang=tts_lang)
201
-
202
- return f"Detected Emotion: {emotion.capitalize()}\n{support}", tts_path
203
-
204
- # ======================= Gradio UI =======================
205
- def clear_text():
206
- return "", ""
207
-
208
- with gr.Blocks(title="🧠 Mental Health Therapy Chatbot") as demo:
209
- gr.Markdown("# 🧠 Mental Health Therapy Chatbot")
210
- gr.Markdown("Supportive space for mental health conversations (English/Arabic)")
211
-
212
- with gr.Tabs():
213
- with gr.Tab("💬 Text Chat"):
214
- gr.Markdown("### Chat about how you're feeling")
215
- with gr.Row():
216
- with gr.Column():
217
- text_input = gr.Textbox(label="Type your message here...", lines=3)
218
- text_submit = gr.Button("Send Message", variant="primary")
219
- with gr.Column():
220
- text_output = gr.Textbox(label="Response", interactive=False, lines=5)
221
- text_submit.click(fn=respond, inputs=[text_input], outputs=[text_output])
222
- clear_btn = gr.Button("Clear Conversation")
223
- clear_btn.click(fn=clear_text, outputs=[text_input, text_output])
224
-
225
- with gr.Tab("🎙️ Voice Chat"):
226
- gr.Markdown("### Speak to me — I'll detect emotion and respond")
227
- with gr.Row():
228
- with gr.Column():
229
- audio_input_v = gr.Audio(sources=["microphone"], type="filepath", label="🎤 Speak Here")
230
- language_input = gr.Radio(["English", "Arabic"], value="English", label="Language")
231
- voice_submit = gr.Button("Process Voice", variant="primary")
232
- with gr.Column():
233
- voice_output_text = gr.Textbox(label="💬 Chatbot Response (text)", lines=6, interactive=False)
234
- voice_output_audio = gr.Audio(label="🔊 Voice Output (TTS)", interactive=False)
235
- voice_submit.click(fn=voice_chat_combined, inputs=[audio_input_v, language_input],
236
- outputs=[voice_output_text, voice_output_audio])
237
 
238
  if __name__ == "__main__":
239
- print("Starting Mental Health Therapy Chatbot...")
240
- demo.launch(share=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ from huggingface_hub import InferenceClient
3
+ import random
4
+ import re
5
+
6
+ # Allowed mental health keywords (EN + AR + transliterated Arabic)
7
+ MENTAL_KEYWORDS = [
8
+ # English
9
+ "depression", "depressed", "anxiety", "anxious", "panic", "stress", "sad", "lonely",
10
+ "trauma", "mental", "therapy", "therapist", "counselor", "mood", "overwhelmed", "anger",
11
+ "fear", "worry", "self-esteem", "confidence", "motivation", "relationship", "cope", "coping",
12
+ "relax", "calm", "sleep", "emotion", "feeling", "feel", "thoughts", "help", "life", "advice",
13
+ "unmotivated", "lost", "hopeless", "tired", "burnout", "cry", "hurt", "love", "breakup",
14
+ "friend", "family", "alone", "heartbroken", "scared", "fearful",
15
+ # Transliterated Arabic
16
+ "ana", "zahqan", "daye2", "ha2t", "mota3ab", "mota3eb", "za3lan", "malo", "khalni", "mash3or",
17
+ "bakhaf", "w7ed", "msh 3aref", "mash fahem", "malish", "3ayez", "ayez", "7azeen", "mdaye2",
18
+ # Arabic
19
+ "حزين", "تعبان", "قلق", "خايف", "وحدة", "ضيق", "توتر", "زعلان", "اكتئاب", "علاج",
20
+ "مشاعر", "مضغوط", "قلقان", "وحدي", "مش مبسوط", "زهقان", "ضايق", "تعب", "مش مرتاح",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  ]
22
 
23
+ # ✅ Off-topic keywords (EN + AR)
24
+ OFF_TOPIC = [
25
+ # English
26
+ "recipe", "song", "music", "lyrics", "joke", "funny", "laugh", "code", "python", "program",
27
+ "game", "food", "cook", "movie", "film", "series", "sport", "football", "instagram",
28
+ "tiktok", "money", "business", "crypto", "ai", "computer",
29
+ # Arabic
30
+ "نكتة", "ضحك", "اغنية", "اغاني", "طبخ", "اكل", "فيلم", "مسلسل", "كورة", "رياضة",
31
+ "بيزنس", "فلوس", "العاب", "لعبة", "كود", "برمجة", "ذكاء اصطناعي"
32
+ ]
33
+
34
+ # ✅ Random natural off-topic responses
35
  OFF_TOPIC_RESPONSES = [
36
+ "I'm here to help with emotional and mental well-being. Let's focus on how you're feeling, coping, or managing your emotions today.",
37
+ "I specialize in mental and emotional health conversations. Tell me what’s been on your mind lately.",
38
+ "Let’s bring it back to how you’ve been feeling — I’m here to help you talk through emotions, stress, or challenges.",
39
+ "My goal is to support your mental health. How have things been emotionally for you lately?",
40
+ "I’m here for emotional and mental support only. What’s been bothering you recently?",
41
+ "Let's focus on your thoughts and feelings — I can help you process or manage them better.",
42
+ "It sounds like you might be going off-topic. Can we talk about how you’ve been feeling instead?",
43
+ "Let’s keep this space focused on your emotions and well-being. What’s been heavy on your mind lately?",
44
  ]
45
 
46
+ # ✅ Detect Arabic characters
47
  def contains_arabic(text: str) -> bool:
48
  return bool(re.search(r"[\u0600-\u06FF]", text))
49
 
50
+ # ✅ Function to check if input is related to mental health
51
  def is_mental_health_related(text: str) -> bool:
52
  text_lower = text.lower()
53
+ has_arabic = contains_arabic(text_lower)
54
+
55
+ # If message includes off-topic Arabic or English terms → block it
56
  if any(word in text_lower for word in OFF_TOPIC):
57
  return False
58
+
59
+ # If it has mental-related Arabic/English → allow
60
  if any(word in text_lower for word in MENTAL_KEYWORDS):
61
  return True
62
+
63
+ # If purely Arabic but not off-topic → assume emotional (allow)
64
+ if has_arabic:
65
  return True
66
+
67
+ # Default fallback
68
  return False
69
 
70
+
71
+ # Main response function
72
+ def respond(
73
+ message,
74
+ history: list[dict[str, str]],
75
+ system_message,
76
+ max_tokens,
77
+ temperature,
78
+ top_p,
79
+ hf_token: gr.OAuthToken,
80
+ ):
81
  if not is_mental_health_related(message):
82
+ yield random.choice(OFF_TOPIC_RESPONSES)
83
+ return
84
+
85
+ locked_system_message = (
86
+ "You are a licensed mental health therapy assistant. "
87
+ "You respond with empathy, emotional intelligence, and a therapeutic tone. "
88
+ "Never answer questions unrelated to emotional or mental wellness, even if they are in another language."
89
+ )
90
+
91
+ client = InferenceClient(token=hf_token.token, model="openai/gpt-oss-20b")
92
+
93
+ messages = [{"role": "system", "content": locked_system_message}]
94
+ messages.extend(history)
95
+ messages.append({"role": "user", "content": message})
96
+
97
+ response = ""
98
+
99
+ for message in client.chat_completion(
100
+ messages,
101
+ max_tokens=max_tokens,
102
+ stream=True,
103
+ temperature=temperature,
104
+ top_p=top_p,
105
+ ):
106
+ choices = message.choices
107
+ token = ""
108
+ if len(choices) and choices[0].delta.content:
109
+ token = choices[0].delta.content
110
+ response += token
111
+ yield response
112
+
113
+
114
+ # Gradio interface setup
115
+ chatbot = gr.ChatInterface(
116
+ respond,
117
+ type="messages",
118
+ additional_inputs=[
119
+ gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
120
+ gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
121
+ gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
122
+ gr.Slider(
123
+ minimum=0.1,
124
+ maximum=1.0,
125
+ value=0.95,
126
+ step=0.05,
127
+ label="Top-p (nucleus sampling)",
128
+ ),
129
+ ],
130
+ )
131
+
132
+ with gr.Blocks() as demo:
133
+ with gr.Sidebar():
134
+ gr.LoginButton()
135
+ chatbot.render()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
  if __name__ == "__main__":
138
+ demo.launch()