cassandrasestier commited on
Commit
811d9dc
·
verified ·
1 Parent(s): c36e1c8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +282 -55
app.py CHANGED
@@ -1,7 +1,6 @@
1
  # ================================
2
- # 🪞 MoodMirror+ — Text Emotion • Advice-only + Emergency Numbers tab
3
- # - Text: GoEmotions (TF-IDF + OneVsRest LR, dataset-only)
4
- # - Adds a "Numéros d'urgence" tab for crisis helplines by country
5
  # ================================
6
  import os
7
  import re
@@ -41,6 +40,7 @@ CLOSING_RE = re.compile(
41
  re.I,
42
  )
43
 
 
44
  CRISIS_NUMBERS = {
45
  "France": "📞 **3114** (Numéro national de prévention du suicide, 24/7)",
46
  "United States": "📞 **988** (Suicide & Crisis Lifeline, 24/7)",
@@ -50,33 +50,202 @@ CRISIS_NUMBERS = {
50
  "Other / Not listed": "Call local emergency (**112/911**) or search “suicide hotline” for your country.",
51
  }
52
 
53
- # ---------------- Advice library ----------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  SUGGESTIONS = {
55
- "sadness": ["Go for a 5-minute outside walk and name three colors you see."],
56
- "fear": ["Do 5-4-3-2-1 grounding: 5 see, 4 feel, 3 hear, 2 smell, 1 taste."],
57
- "anger": ["Take space before replying; set a 10-minute timer."],
58
- "nervousness": ["4-7-8 breathing: in 4s, hold 7s, out 8s (four rounds)."],
59
- "boredom": ["Set a 2-minute timer and start anything small."],
60
- "grief": ["Hold a photo or object and say their name softly."],
61
- "love": ["Send a kind message without expecting a reply."],
62
- "joy": ["Pause and take three slow breaths to savor this."],
63
- "curiosity": ["Search one concept and read just the first paragraph."],
64
- "gratitude": ["List three tiny things that made today easier."],
65
- "neutral": ["Take one slow breath and relax your hands."],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  }
67
 
 
68
  WHY_BY_EMOTION = {
69
- "sadness": "Small sensory and connection cues can ease low mood.",
70
- "fear": "Grounding + longer exhales calm the threat system.",
71
- "anger": "Space + movement lower adrenaline to respond, not react.",
72
- "nervousness": "Slow breathing and micro-actions reduce anxious energy.",
73
- "boredom": "Novelty and small starts re-engage attention.",
74
- "grief": "Rituals and gentle care help carry love and loss.",
75
- "love": "Expressing care strengthens bonds and self-kindness.",
76
- "joy": "Savoring and sharing consolidate positive memories.",
77
- "curiosity": "Small explorations feed learning and perspective.",
78
- "gratitude": "Noticing support shifts attention toward strengths.",
79
- "neutral": "Simple body care keeps your baseline steady.",
80
  }
81
 
82
  COLOR_MAP = {
@@ -87,9 +256,19 @@ COLOR_MAP = {
87
  "neutral": "#F5F5F5", "curiosity": "#E6EE9C",
88
  }
89
 
90
- GOEMO_TO_APP = {"sadness": "sadness", "joy": "joy", "fear": "fear", "anger": "anger", "neutral": "neutral"}
 
 
 
 
 
 
 
 
 
 
91
 
92
- # ---------------- Helpers ----------------
93
  THRESHOLD_BASE = 0.30
94
  MIN_THRESHOLD = 0.10
95
  CLEAN_RE = re.compile(r"(https?://\S+)|(@\w+)|(#\w+)|[^a-zA-Z0-9\s']")
@@ -100,8 +279,37 @@ def clean_text(s: str) -> str:
100
  s = re.sub(r"\s+", " ", s).strip()
101
  return s
102
 
 
 
 
 
 
 
 
 
 
 
 
103
  def augment_text(text: str, history=None) -> str:
104
- return clean_text(text or "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
 
106
  # ---------------- SQLite ----------------
107
  def get_conn():
@@ -123,7 +331,7 @@ def log_session(country, msg, emotion):
123
  conn.commit()
124
  conn.close()
125
 
126
- # ---------------- Model ----------------
127
  def load_goemotions_dataset():
128
  ds = load_dataset("google-research-datasets/go_emotions", "simplified")
129
  return ds, ds["train"].features["labels"].feature.names
@@ -139,7 +347,10 @@ def train_or_load_model():
139
  Y_train = mlb.fit_transform(y_train)
140
  clf = Pipeline([
141
  ("tfidf", TfidfVectorizer(lowercase=True, ngram_range=(1,2), min_df=2, max_df=0.9, strip_accents="unicode")),
142
- ("ovr", OneVsRestClassifier(LogisticRegression(solver="saga", max_iter=1000, class_weight="balanced"), n_jobs=-1))
 
 
 
143
  ])
144
  clf.fit(X_train, Y_train)
145
  joblib.dump({"version": MODEL_VERSION, "pipeline": clf, "mlb": mlb, "label_names": names}, MODEL_PATH)
@@ -151,6 +362,7 @@ except Exception as e:
151
  print("[ERROR] Model load/train:", e)
152
  CLASSIFIER, MLB, LABEL_NAMES = None, None, None
153
 
 
154
  def classify_text(text_augmented: str):
155
  if not CLASSIFIER: return []
156
  proba = CLASSIFIER.predict_proba([text_augmented])[0]
@@ -170,7 +382,7 @@ def detect_emotion_text(message: str, history):
170
  bucket[app] = max(bucket.get(app, 0.0), p)
171
  return max(bucket, key=bucket.get) if bucket else "neutral"
172
 
173
- # ---------------- Advice logic ----------------
174
  def pick_advice_from_pool(emotion: str, pool: dict, last_tip: str = ""):
175
  tips_all = SUGGESTIONS.get(emotion, SUGGESTIONS["neutral"])
176
  entry = pool.get(emotion, {"unused": [], "last": ""})
@@ -187,10 +399,15 @@ def format_reply(emotion: str, tip: str) -> str:
187
  why = WHY_BY_EMOTION.get(emotion, WHY_BY_EMOTION["neutral"])
188
  return f"Try this now:\n• {tip}\n_(Why it helps: {why})_"
189
 
 
190
  def crisis_block(country):
191
  msg = CRISIS_NUMBERS.get(country, CRISIS_NUMBERS["Other / Not listed"])
192
  return f"💛 You matter. If you're in danger or thinking of harming yourself, please reach out now.\n\n{msg}"
193
 
 
 
 
 
194
  def chat_step(user_text, history, country, save_session, advice_pool):
195
  if user_text and CRISIS_RE.search(user_text):
196
  return crisis_block(country), "#FFD6E7", "neutral", "", advice_pool
@@ -212,11 +429,15 @@ init_db()
212
 
213
  with gr.Blocks(title="🪞 MoodMirror+ — Text Emotion • Advice-only") as demo:
214
  style = gr.HTML("")
215
- gr.Markdown("### 🪞 MoodMirror+ — Emotion-aware advice\n_Not medical advice. If unsafe, please reach out for help._")
 
 
 
 
216
 
217
  with gr.Tabs():
218
- # Tab 1 Conseils
219
- with gr.Tab("Conseils"):
220
  with gr.Row():
221
  country = gr.Dropdown(list(CRISIS_NUMBERS.keys()), value="Other / Not listed", label="Country")
222
  save_ok = gr.Checkbox(False, label="Save anonymized session")
@@ -244,42 +465,48 @@ with gr.Blocks(title="🪞 MoodMirror+ — Text Emotion • Advice-only") as dem
244
  reply = format_reply(_emotion, tip)
245
  return chat_hist + [[None, reply]], "", _emotion, tip, _pool
246
 
247
- send.click(respond,
 
248
  inputs=[msg, chat, country, save_ok, last_emotion, last_tip, advice_pool],
249
  outputs=[chat, style, last_emotion, last_tip, advice_pool],
250
- queue=True)
251
- msg.submit(respond,
 
 
252
  inputs=[msg, chat, country, save_ok, last_emotion, last_tip, advice_pool],
253
  outputs=[chat, style, last_emotion, last_tip, advice_pool],
254
- queue=True)
255
- regen.click(new_advice,
 
 
256
  inputs=[chat, last_emotion, last_tip, advice_pool],
257
  outputs=[chat, style, last_emotion, last_tip, advice_pool],
258
- queue=True)
 
259
 
260
- # Tab 2 Numéros d'urgence
261
- with gr.Tab("Numéros d'urgence"):
262
  gr.Markdown(
263
- "#### 📟 Numéros d’urgence par pays\n"
264
- "Sélectionne ton pays pour voir la ligne d’aide recommandée.\n"
265
- "En cas de danger immédiat, compose **112** ou **911** selon ton pays."
266
  )
267
 
268
  country_view = gr.Dropdown(
269
- choices=list(CRISIS_NUMBERS.keys()),
270
- value="France" if "France" in CRISIS_NUMBERS else list(CRISIS_NUMBERS.keys())[0],
271
- label="Pays"
272
  )
273
- crisis_info = gr.Markdown(value=crisis_block("France") if "France" in CRISIS_NUMBERS else crisis_block("Other / Not listed"))
274
 
275
- def show_crisis_for_country(c):
276
- return crisis_block(c)
277
 
278
- country_view.change(show_crisis_for_country, inputs=country_view, outputs=crisis_info)
279
 
280
  gr.Markdown(
281
- "> ℹ️ Ces numéros sont fournis à titre informatif. "
282
- "Si ton pays n’est pas listé, contacte les services d’urgence locaux (**112/911**)."
283
  )
284
 
285
  if __name__ == "__main__":
 
1
  # ================================
2
+ # 🪞 MoodMirror+ — Text Emotion • Advice-only + brief intros & reasons
3
+ # - Adds an English "Emergency numbers" tab by country
 
4
  # ================================
5
  import os
6
  import re
 
40
  re.I,
41
  )
42
 
43
+ # (Original mapping kept for in-chat crisis block)
44
  CRISIS_NUMBERS = {
45
  "France": "📞 **3114** (Numéro national de prévention du suicide, 24/7)",
46
  "United States": "📞 **988** (Suicide & Crisis Lifeline, 24/7)",
 
50
  "Other / Not listed": "Call local emergency (**112/911**) or search “suicide hotline” for your country.",
51
  }
52
 
53
+ # --------- English mapping used by the new tab (expanded) ----------
54
+ CRISIS_NUMBERS_EN = {
55
+ # Core set you already had
56
+ "United States": "📞 **988** (Suicide & Crisis Lifeline, 24/7)",
57
+ "Canada": "📞 **988** (Suicide Crisis Helpline, 24/7)",
58
+ "United Kingdom": "📞 **116 123** (Samaritans, 24/7)",
59
+ "Ireland": "📞 **116 123** (Samaritans Ireland, 24/7)",
60
+ "France": "📞 **3114** (National Suicide Prevention number, 24/7)",
61
+ "Australia": "📞 **13 11 14** (Lifeline, 24/7)",
62
+
63
+ # Added countries (verified public resources)
64
+ "Belgium": "📞 **1813** (Zelfmoordlijn, 24/7)",
65
+ "Switzerland": "📞 **143** (La Main Tendue / Heart2Heart)",
66
+ "Spain": "📞 **024** (Línea 024 — Atención a la conducta suicida, 24/7)",
67
+ "Germany": "📞 **0800 111 0 111** / **0800 111 0 222** / **116 123** (TelefonSeelsorge, 24/7)",
68
+ "Netherlands": "📞 **0800-0113** (free) or **113** (standard rate) — 113 Suicide Prevention",
69
+ "Portugal": "📞 **213 544 545**, **912 802 669**, **963 524 660** (SOS Voz Amiga)",
70
+ "New Zealand": "📞 **0508 828 865** (Suicide Crisis Helpline — TAUTOKO)",
71
+ "India": "📞 **14416** (Tele MANAS, 24/7) or **1800-599-0019** (KIRAN)",
72
+ "South Africa": "📞 **0800 567 567** (SADAG Suicide Crisis Helpline, 24/7)",
73
+
74
+ # Fallback
75
+ "Other / Not listed": "Call local emergency (**112/911**) or search “suicide hotline” + your country.",
76
+ }
77
+
78
+ # ---------------- Advice library (concise, actionable) ----------------
79
  SUGGESTIONS = {
80
+ "sadness": [
81
+ "Go for a 5-minute outside walk and name three colors you see.",
82
+ "Write what hurts, then add one thing you still care about.",
83
+ "Take a warm shower and focus on the feeling on your shoulders.",
84
+ "Message a safe person: “Can I vent for 2 minutes?”",
85
+ "Eat something simple and drink a big glass of water.",
86
+ "Play one song that matches your mood don’t hide it.",
87
+ "Wrap yourself in a blanket and slow your exhale for 60 seconds.",
88
+ "List three small things that kept you going today.",
89
+ "Tidy one tiny area (corner of the desk or sink).",
90
+ "Watch something gentle or nostalgic for 10 minutes.",
91
+ "Place a hand on your chest and repeat: “This will pass.”",
92
+ "Write a note to your future self: “You made it through today.”",
93
+ ],
94
+ "fear": [
95
+ "Do 5-4-3-2-1 grounding: 5 see, 4 feel, 3 hear, 2 smell, 1 taste.",
96
+ "Make your exhale longer than your inhale for eight breaths.",
97
+ "Turn on a light or soft music to signal safety to your body.",
98
+ "Name the fear out loud in one sentence.",
99
+ "Hold something cold in your hand for 30 seconds.",
100
+ "Tell yourself: “Just the next minute — that’s all.”",
101
+ "Stand up and shake out your hands and arms.",
102
+ "Write the worst case, then the most likely case beside it.",
103
+ "Walk while counting your steps slowly to 100.",
104
+ "Mute news/scrolling for the next 30 minutes.",
105
+ "Sit with back supported and feet flat; feel the contact points.",
106
+ "Repeat softly: “This feeling is temporary.”",
107
+ ],
108
+ "anger": [
109
+ "Take space before replying; set a 10-minute timer.",
110
+ "Do ten slow exhales through pursed lips.",
111
+ "Write an uncensored note and delete it afterward.",
112
+ "Splash cool water on your face or wrists.",
113
+ "Walk fast for five minutes or climb one set of stairs.",
114
+ "Name the crossed boundary and craft one calm sentence.",
115
+ "Squeeze then release your fists ten times.",
116
+ "Clean a small area to discharge energy.",
117
+ "Postpone the decision until you feel steady again.",
118
+ "Say: “I’m not ready to talk yet; I’ll come back later.”",
119
+ "Ask yourself: “What hurt sits under this anger?”",
120
+ "Drop your shoulders and unclench your jaw.",
121
+ ],
122
+ "nervousness": [
123
+ "4-7-8 breathing: in 4s, hold 7s, out 8s (four rounds).",
124
+ "Relax your jaw and lower your shoulders.",
125
+ "Write worries down; cross out what you can’t control.",
126
+ "Pick one tiny action you can finish in five minutes.",
127
+ "Trace a square with your finger; breathe in on each side.",
128
+ "Walk while matching steps to slower breaths.",
129
+ "Sit by daylight or open a window for fresh air.",
130
+ "Say: “I can do this one step at a time.”",
131
+ "Pause caffeine for the next few hours.",
132
+ "Give yourself a 2-minute pause — eyes on one calm point.",
133
+ "Hold a warm mug and notice the heat.",
134
+ "Stretch your arms overhead and widen your posture.",
135
+ ],
136
+ "boredom": [
137
+ "Set a 2-minute timer and start anything small.",
138
+ "Change your soundtrack — put on one new song.",
139
+ "Shift a few objects on your desk for a new view.",
140
+ "Read one paragraph on a random topic.",
141
+ "Doodle or hum for 90 seconds without judging it.",
142
+ "Step outside; look up and find three shapes in the sky.",
143
+ "Write five ideas quickly without editing.",
144
+ "Do 15 jumping jacks or a quick stretch.",
145
+ "Clean your phone screen or keyboard.",
146
+ "Try a different drink or snack.",
147
+ "Learn one keyboard shortcut you’ll use today.",
148
+ "Send a simple “how are you?” to someone.",
149
+ ],
150
+ "grief": [
151
+ "Hold a photo or object and say their name softly.",
152
+ "Drink water and eat something — your body grieves too.",
153
+ "Write a short letter to them about today.",
154
+ "Rest without guilt — sorrow is heavy work.",
155
+ "Share one memory you want to keep vivid.",
156
+ "Let tears come when they need to.",
157
+ "Light a candle and sit quietly for two minutes.",
158
+ "Walk somewhere meaningful and notice what rises.",
159
+ "Create a small ritual to honor them (song, place, phrase).",
160
+ "Plan one kind thing for yourself this week.",
161
+ "Say: “Missing you means I loved you.”",
162
+ "Plant your feet and breathe into your belly.",
163
+ ],
164
+ "love": [
165
+ "Send a kind message without expecting a reply.",
166
+ "Note three things you appreciate about someone close.",
167
+ "Offer yourself one gentle act you needed today.",
168
+ "Listen fully to someone for one uninterrupted minute.",
169
+ "Give a sincere compliment to a stranger.",
170
+ "Prepare something with care for someone you value.",
171
+ "Say “thank you” out loud for one small thing.",
172
+ "Ask a caring question and wait for the answer.",
173
+ "Write what love means to you in three lines.",
174
+ "Plan a tiny gesture for tomorrow.",
175
+ "Look at your face kindly in the mirror for 10 seconds.",
176
+ "Let yourself accept help today.",
177
+ ],
178
+ "joy": [
179
+ "Pause and take three slow breaths to savor this.",
180
+ "Capture it — photo, note, or voice memo.",
181
+ "Tell someone why you feel good right now.",
182
+ "Move to music for one song.",
183
+ "Notice where the joy sits in your body.",
184
+ "Prepare a small treat to celebrate.",
185
+ "Write one line starting with “I’m glad that…”.",
186
+ "Do one kind act while you feel resourced.",
187
+ "Enjoy one minute of quiet appreciation.",
188
+ "Plan a tiny celebration later today.",
189
+ "Share a smile with someone nearby.",
190
+ "Thank yourself for the steps that led here.",
191
+ ],
192
+ "curiosity": [
193
+ "Search one concept and read just the first paragraph.",
194
+ "Ask a question you’ve never asked a friend.",
195
+ "Watch a “how does X work?” video for 3 minutes.",
196
+ "Write three quick “what if…?” ideas.",
197
+ "Take apart a small idea or object (safely) and observe.",
198
+ "Teach someone one thing you learned today.",
199
+ "Open a random article and summarize it in one line.",
200
+ "Try a new route or viewpoint in your space.",
201
+ "Learn one new word and use it once.",
202
+ "List five topics you’d like to explore.",
203
+ "Read one thread in a community you care about.",
204
+ "Sketch a simple diagram of an idea.",
205
+ ],
206
+ "gratitude": [
207
+ "List three tiny things that made today easier.",
208
+ "Thank someone by name for something specific.",
209
+ "Notice an everyday object and appreciate its help.",
210
+ "Take a photo of a small comfort.",
211
+ "Write “I’m lucky that…” and finish it once.",
212
+ "Send a short “thinking of you”.",
213
+ "Savor your next sip or bite with attention.",
214
+ "Name one privilege you have today.",
215
+ "Say thank you silently to your body.",
216
+ "Share one good thing with a friend.",
217
+ "Keep a short gratitude note in your phone.",
218
+ "Place a small reminder where you’ll see it tomorrow.",
219
+ ],
220
+ "neutral": [
221
+ "Take one slow breath and relax your hands.",
222
+ "Notice a color, a texture, and a sound around you.",
223
+ "Plan one tiny task to finish today.",
224
+ "Drink a glass of water mindfully.",
225
+ "Stand, stretch, and roll your shoulders.",
226
+ "Step outside for two minutes of fresh air.",
227
+ "Wipe your screen or desk for a reset.",
228
+ "Organize three items in your space.",
229
+ "Set a 10-minute timer to focus on one thing.",
230
+ "Do a gentle 30-second neck stretch.",
231
+ "Check your posture; support your back.",
232
+ "Open a window and take three breaths.",
233
+ ],
234
  }
235
 
236
+ # One-line reasons
237
  WHY_BY_EMOTION = {
238
+ "sadness": "Small sensory and connection cues can ease low mood and restore momentum.",
239
+ "fear": "Grounding + longer exhales calm the threat system and signal safety.",
240
+ "anger": "Space + movement lower adrenaline so you can respond, not react.",
241
+ "nervousness": "Slow breathing and micro-actions reduce anxious energy and create control.",
242
+ "boredom": "Novelty and tiny starts re-engage attention and kick-off motivation.",
243
+ "grief": "Rituals and gentle care help your body carry love and loss together.",
244
+ "love": "Expressing and receiving care strengthens bonds and self-kindness.",
245
+ "joy": "Savoring and sharing consolidate positive memories and resilience.",
246
+ "curiosity": "Small explorations feed learning circuits and open perspective.",
247
+ "gratitude": "Noticing support shifts attention toward resources and strengths.",
248
+ "neutral": "Simple body care keeps your baseline steady for the rest of the day.",
249
  }
250
 
251
  COLOR_MAP = {
 
256
  "neutral": "#F5F5F5", "curiosity": "#E6EE9C",
257
  }
258
 
259
+ # Map 28 GoEmotions -> UI buckets
260
+ GOEMO_TO_APP = {
261
+ "admiration": "gratitude", "amusement": "joy", "anger": "anger", "annoyance": "anger",
262
+ "approval": "gratitude", "caring": "love", "confusion": "nervousness",
263
+ "curiosity": "curiosity", "desire": "joy", "disappointment": "sadness",
264
+ "disapproval": "anger", "disgust": "anger", "embarrassment": "nervousness",
265
+ "excitement": "joy", "fear": "fear", "gratitude": "gratitude", "grief": "grief",
266
+ "joy": "joy", "love": "love", "nervousness": "nervousness", "optimism": "joy",
267
+ "pride": "joy", "realization": "neutral", "relief": "gratitude", "remorse": "grief",
268
+ "sadness": "sadness", "surprise": "neutral", "neutral": "neutral",
269
+ }
270
 
271
+ # ---------------- Preprocessing & thresholds (text) ----------------
272
  THRESHOLD_BASE = 0.30
273
  MIN_THRESHOLD = 0.10
274
  CLEAN_RE = re.compile(r"(https?://\S+)|(@\w+)|(#\w+)|[^a-zA-Z0-9\s']")
 
279
  s = re.sub(r"\s+", " ", s).strip()
280
  return s
281
 
282
+ EMOJI_HINTS = {"😭": "sadness", "😡": "anger", "🥰": "love", "😨": "fear", "😴": "boredom"}
283
+ NEGATION_HINTS_EN = {
284
+ "not happy": "sadness", "not ok": "sadness", "no energy": "boredom",
285
+ "can't focus": "nervousness", "cannot focus": "nervousness"
286
+ }
287
+ HINTS_FR = {
288
+ "pas bien": "sadness", "triste": "sadness", "j'ai peur": "fear",
289
+ "angoisse": "nervousness", "anxieux": "nervousness",
290
+ "fatigué": "sadness", "épuisé": "sadness",
291
+ }
292
+
293
  def augment_text(text: str, history=None) -> str:
294
+ t = clean_text(text or "")
295
+ hints = []
296
+ for k in EMOJI_HINTS:
297
+ if k in (text or ""):
298
+ hints.append(EMOJI_HINTS[k])
299
+ for k in NEGATION_HINTS_EN:
300
+ if k in t:
301
+ hints.append(NEGATION_HINTS_EN[k])
302
+ lt = (text or "").lower()
303
+ for k in HINTS_FR:
304
+ if k in lt:
305
+ hints.append(HINTS_FR[k])
306
+ if history and len(t.split()) < 8:
307
+ prev_user = history[-1][0] if history and history[-1] else ""
308
+ if isinstance(prev_user, str) and prev_user:
309
+ t = t + " " + clean_text(prev_user)
310
+ if hints:
311
+ t = t + " " + " ".join([f"emo_{h}" for h in hints])
312
+ return t
313
 
314
  # ---------------- SQLite ----------------
315
  def get_conn():
 
331
  conn.commit()
332
  conn.close()
333
 
334
+ # ---------------- Text model: GoEmotions dataset-only ----------------
335
  def load_goemotions_dataset():
336
  ds = load_dataset("google-research-datasets/go_emotions", "simplified")
337
  return ds, ds["train"].features["labels"].feature.names
 
347
  Y_train = mlb.fit_transform(y_train)
348
  clf = Pipeline([
349
  ("tfidf", TfidfVectorizer(lowercase=True, ngram_range=(1,2), min_df=2, max_df=0.9, strip_accents="unicode")),
350
+ ("ovr", OneVsRestClassifier(
351
+ LogisticRegression(solver="saga", max_iter=1000, class_weight="balanced"),
352
+ n_jobs=-1
353
+ ))
354
  ])
355
  clf.fit(X_train, Y_train)
356
  joblib.dump({"version": MODEL_VERSION, "pipeline": clf, "mlb": mlb, "label_names": names}, MODEL_PATH)
 
362
  print("[ERROR] Model load/train:", e)
363
  CLASSIFIER, MLB, LABEL_NAMES = None, None, None
364
 
365
+ # ---------------- Inference: TEXT ----------------
366
  def classify_text(text_augmented: str):
367
  if not CLASSIFIER: return []
368
  proba = CLASSIFIER.predict_proba([text_augmented])[0]
 
382
  bucket[app] = max(bucket.get(app, 0.0), p)
383
  return max(bucket, key=bucket.get) if bucket else "neutral"
384
 
385
+ # ---------------- Advice selection ----------------
386
  def pick_advice_from_pool(emotion: str, pool: dict, last_tip: str = ""):
387
  tips_all = SUGGESTIONS.get(emotion, SUGGESTIONS["neutral"])
388
  entry = pool.get(emotion, {"unused": [], "last": ""})
 
399
  why = WHY_BY_EMOTION.get(emotion, WHY_BY_EMOTION["neutral"])
400
  return f"Try this now:\n• {tip}\n_(Why it helps: {why})_"
401
 
402
+ # ---------------- Replies ----------------
403
  def crisis_block(country):
404
  msg = CRISIS_NUMBERS.get(country, CRISIS_NUMBERS["Other / Not listed"])
405
  return f"💛 You matter. If you're in danger or thinking of harming yourself, please reach out now.\n\n{msg}"
406
 
407
+ def crisis_block_en(country):
408
+ msg = CRISIS_NUMBERS_EN.get(country, CRISIS_NUMBERS_EN["Other / Not listed"])
409
+ return "💛 You matter. If you're in danger or thinking of harming yourself, please reach out now.\n\n" + msg
410
+
411
  def chat_step(user_text, history, country, save_session, advice_pool):
412
  if user_text and CRISIS_RE.search(user_text):
413
  return crisis_block(country), "#FFD6E7", "neutral", "", advice_pool
 
429
 
430
  with gr.Blocks(title="🪞 MoodMirror+ — Text Emotion • Advice-only") as demo:
431
  style = gr.HTML("")
432
+ gr.Markdown(
433
+ "### 🪞 MoodMirror+ — Emotion-aware advice (text-only)\n"
434
+ "Classifier trained on GoEmotions (dataset-only). Each message is analysed anew.\n\n"
435
+ "_Not medical advice. If unsafe, please reach out for help._"
436
+ )
437
 
438
  with gr.Tabs():
439
+ # ---- Tab 1: Advice (chat) ----
440
+ with gr.Tab("Advice"):
441
  with gr.Row():
442
  country = gr.Dropdown(list(CRISIS_NUMBERS.keys()), value="Other / Not listed", label="Country")
443
  save_ok = gr.Checkbox(False, label="Save anonymized session")
 
465
  reply = format_reply(_emotion, tip)
466
  return chat_hist + [[None, reply]], "", _emotion, tip, _pool
467
 
468
+ send.click(
469
+ respond,
470
  inputs=[msg, chat, country, save_ok, last_emotion, last_tip, advice_pool],
471
  outputs=[chat, style, last_emotion, last_tip, advice_pool],
472
+ queue=True
473
+ )
474
+ msg.submit(
475
+ respond,
476
  inputs=[msg, chat, country, save_ok, last_emotion, last_tip, advice_pool],
477
  outputs=[chat, style, last_emotion, last_tip, advice_pool],
478
+ queue=True
479
+ )
480
+ regen.click(
481
+ new_advice,
482
  inputs=[chat, last_emotion, last_tip, advice_pool],
483
  outputs=[chat, style, last_emotion, last_tip, advice_pool],
484
+ queue=True
485
+ )
486
 
487
+ # ---- Tab 2: Emergency numbers (EN) ----
488
+ with gr.Tab("Emergency numbers"):
489
  gr.Markdown(
490
+ "#### 📟 Emergency numbers by country\n"
491
+ "Select your country to see the recommended crisis line. "
492
+ "If you’re in immediate danger, call local emergency services (**112/911**) right away."
493
  )
494
 
495
  country_view = gr.Dropdown(
496
+ choices=list(CRISIS_NUMBERS_EN.keys()),
497
+ value="United States",
498
+ label="Country"
499
  )
500
+ crisis_info = gr.Markdown(value=crisis_block_en("United States"))
501
 
502
+ def show_crisis_for_country_en(c):
503
+ return crisis_block_en(c)
504
 
505
+ country_view.change(show_crisis_for_country_en, inputs=country_view, outputs=crisis_info)
506
 
507
  gr.Markdown(
508
+ "> ℹ️ Numbers may change. If a line doesn’t work or your country isn’t listed, "
509
+ "call local emergency services (**112/911**) or search **“suicide hotline” + your country**."
510
  )
511
 
512
  if __name__ == "__main__":