MindCare-AI / app.py
mlnjsh's picture
fix: switch Audio/Image to type=filepath for mic/webcam capture
9bf7f95 verified
Raw
History Blame Contribute Delete
79.7 kB
"""
MindCare AI — Advanced Mental Health Support Bot
==================================================
10x Features over Wysa, Woebot (now DEAD), Youper:
- Real emotion detection via multi-dimensional sentiment analysis
- 6 therapy modalities: CBT, DBT, ACT, Motivational Interviewing, Psychoeducation, Mindfulness
- Crisis detection pipeline with emergency resources
- Session continuity — remembers therapeutic progress
- Mood tracking with visual analytics
- Journaling integration with AI reflections
- PHQ-9 and GAD-7 validated screening tools
- Safety-first with clear escalation pathways
Hosted on Hugging Face Spaces with Gradio
"""
import gradio as gr
import json
import re
import math
import numpy as np
from datetime import datetime
from collections import Counter, defaultdict
# Lazy-loaded heavy libraries (loaded on first use to speed up startup)
_librosa = None
_hsemotion = None
_cv2 = None
def _get_librosa():
global _librosa
if _librosa is None:
import librosa
_librosa = librosa
return _librosa
def _get_cv2():
global _cv2
if _cv2 is None:
import cv2
_cv2 = cv2
return _cv2
def _get_hsemotion():
global _hsemotion
if _hsemotion is None:
from hsemotion_onnx.facial_emotions import HSEmotionRecognizer
_hsemotion = HSEmotionRecognizer(model_name="enet_b0_8_best_afew")
return _hsemotion
# ---------------------------------------------------------------------------
# RAG Knowledge Base — WHO, DSM-5, APA Guidelines, Research Papers, EHR Data
# ---------------------------------------------------------------------------
MENTAL_HEALTH_KNOWLEDGE = [
# === WHO GUIDELINES ===
{"text": "WHO mhGAP Intervention Guide 2024: Depression is the leading cause of disability worldwide, affecting 280 million people. WHO recommends a stepped-care model: Step 1 — psychoeducation, self-help, active monitoring. Step 2 — brief psychological interventions (problem-solving therapy, behavioral activation). Step 3 — antidepressant medication (fluoxetine or amitriptyline as first-line in low-resource settings) plus psychological treatment. Step 4 — specialist care for treatment-resistant cases. Key principle: combining psychotherapy and medication is more effective than either alone.", "source": "WHO mhGAP Intervention Guide 2024", "category": "who_guideline", "topic": "depression"},
{"text": "WHO Suicide Prevention Report 2024: Over 700,000 people die by suicide annually worldwide. For every suicide, there are more than 20 suicide attempts. Risk factors: previous suicide attempt (strongest predictor), mental disorders, substance abuse, chronic pain, job/financial loss, relationship problems, access to means. Protective factors: strong social connections, access to mental health care, problem-solving skills, cultural/religious beliefs against suicide. WHO recommends means restriction, responsible media reporting, school-based interventions, and follow-up care after attempts.", "source": "WHO Suicide Prevention Report 2024", "category": "who_guideline", "topic": "crisis"},
{"text": "WHO guidelines on mental health in primary care 2024: 75% of people with mental disorders in low/middle-income countries receive no treatment. Integration of mental health into primary care is the most viable way to close the treatment gap. WHO recommends screening with validated tools (PHQ-9 for depression, GAD-7 for anxiety), brief interventions by trained non-specialist health workers, and task-shifting approaches. Pharmacological management: SSRIs as first-line for depression and anxiety; avoid benzodiazepines for long-term use.", "source": "WHO Mental Health in Primary Care 2024", "category": "who_guideline", "topic": "general"},
{"text": "WHO substance use disorder guidelines 2024: 283 million people aged 15-64 have alcohol use disorder worldwide. AUDIT (Alcohol Use Disorders Identification Test) score >=8 indicates hazardous drinking, >=20 indicates likely dependence. Brief interventions (FRAMES: Feedback, Responsibility, Advice, Menu of options, Empathy, Self-efficacy) are effective for hazardous use. For alcohol dependence: medically supervised detoxification, naltrexone or acamprosate for relapse prevention, CBT/motivational enhancement therapy. Opioid use disorder: medication-assisted treatment with buprenorphine or methadone is first-line.", "source": "WHO Substance Use Guidelines 2024", "category": "who_guideline", "topic": "substance_abuse"},
# === CLINICAL GUIDELINES / DSM-5 ===
{"text": "DSM-5 Major Depressive Disorder criteria: Five or more symptoms during the same 2-week period, representing a change from previous functioning. At least one must be depressed mood or loss of interest/pleasure. Other symptoms: significant weight change, insomnia/hypersomnia, psychomotor agitation/retardation, fatigue, worthlessness/excessive guilt, diminished concentration, recurrent thoughts of death. Symptoms cause clinically significant distress or impairment. Not attributable to substance use or medical condition. Specifiers: with anxious distress, with melancholic features, with psychotic features, with seasonal pattern, peripartum onset.", "source": "DSM-5-TR Diagnostic Criteria 2024", "category": "clinical_guideline", "topic": "depression"},
{"text": "DSM-5 Generalized Anxiety Disorder criteria: Excessive anxiety and worry occurring more days than not for at least 6 months about a number of events or activities. The individual finds it difficult to control the worry. Associated with three or more of: restlessness, easily fatigued, difficulty concentrating, irritability, muscle tension, sleep disturbance. Causes clinically significant distress or impairment. Not attributable to substance or medical condition. Differentiate from: panic disorder (discrete episodes), social anxiety (performance situations), OCD (obsessions/compulsions), PTSD (trauma-related).", "source": "DSM-5-TR Diagnostic Criteria 2024", "category": "clinical_guideline", "topic": "anxiety"},
{"text": "APA Practice Guidelines for Depression 2024: First-line treatments — CBT and pharmacotherapy (SSRIs) equally effective for mild-moderate depression. For moderate-severe: combination therapy recommended (psychotherapy + medication). SSRI choice considerations: sertraline (broad evidence, fewer drug interactions), escitalopram (well-tolerated, effective), fluoxetine (long half-life, lower discontinuation syndrome risk). Adequate trial: 4-8 weeks at therapeutic dose. If partial response: augmentation with bupropion, lithium, or aripiprazole. Treatment-resistant depression (failed 2+ adequate trials): consider ECT, TMS, ketamine/esketamine.", "source": "APA Practice Guidelines for Depression 2024", "category": "clinical_guideline", "topic": "depression"},
{"text": "PHQ-9 Clinical Interpretation Guide: Scores 0-4 (minimal depression): monitoring, psychoeducation. Scores 5-9 (mild): watchful waiting, consider brief intervention, reassess in 2-4 weeks. Scores 10-14 (moderate): treatment planning — psychotherapy and/or medication recommended. Scores 15-19 (moderately severe): active treatment with combined therapy. Scores 20-27 (severe): immediate active treatment, consider psychiatric referral. Question 9 (self-harm thoughts): any score >=1 requires risk assessment regardless of total score. The PHQ-9 has sensitivity of 88% and specificity of 88% for major depression at cutoff >=10.", "source": "Kroenke et al., JGIM 2001; PHQ Clinical Guide 2024", "category": "clinical_guideline", "topic": "depression"},
{"text": "GAD-7 Clinical Interpretation Guide: Scores 0-4 (minimal anxiety): monitoring only. Scores 5-9 (mild anxiety): brief interventions, relaxation techniques, reassess. Scores 10-14 (moderate anxiety): consider therapy (CBT) and/or medication (SSRI/SNRI). Scores 15-21 (severe anxiety): active treatment recommended, psychiatric referral. The GAD-7 has sensitivity of 89% and specificity of 82% for generalized anxiety disorder at cutoff >=10. Also screens for panic disorder (sensitivity 74%), social anxiety (sensitivity 72%), and PTSD (sensitivity 66%).", "source": "Spitzer et al., Archives of Internal Medicine 2006", "category": "clinical_guideline", "topic": "anxiety"},
{"text": "Columbia Suicide Severity Rating Scale (C-SSRS): Structured assessment of suicidal ideation and behavior. Ideation levels: 1) Wish to be dead, 2) Non-specific active suicidal thoughts, 3) Active ideation with any methods without plan, 4) Active ideation with some intent without plan, 5) Active ideation with specific plan and intent. Behavior assessment: actual attempt, interrupted attempt, aborted attempt, preparatory acts, non-suicidal self-injury. High risk indicators: ideation level 4-5, any recent behavior, access to means, history of attempts, psychiatric diagnosis, substance use, social isolation.", "source": "Columbia Protocol (C-SSRS) Clinical Guide 2024", "category": "clinical_guideline", "topic": "crisis"},
{"text": "DSM-5 PTSD Diagnostic Criteria: Exposure to actual or threatened death, serious injury, or sexual violence (directly, witnessed, learning about close person, or repeated professional exposure). Intrusion symptoms: recurrent distressing memories/dreams, flashbacks, psychological distress at cues, physiological reactions to cues. Avoidance of stimuli. Negative alterations in cognition/mood: inability to remember aspects, negative beliefs, distorted blame, diminished interest, detachment, restricted affect. Arousal alterations: irritability, reckless behavior, hypervigilance, exaggerated startle, concentration problems, sleep disturbance. Duration >1 month.", "source": "DSM-5-TR Diagnostic Criteria 2024", "category": "clinical_guideline", "topic": "ptsd"},
# === THERAPY EVIDENCE BASE ===
{"text": "Cognitive Behavioral Therapy (CBT) evidence base: CBT is the most extensively researched form of psychotherapy. Meta-analyses show large effect sizes for depression (d=0.71), anxiety disorders (d=0.73), OCD (d=0.98), PTSD (d=0.62), and insomnia (d=0.98). Core model: situations trigger automatic thoughts, which influence emotions and behaviors. Key techniques include cognitive restructuring (identifying and challenging distorted thoughts), behavioral experiments, exposure therapy, behavioral activation, and problem-solving. CBT typically involves 12-20 structured sessions. Relapse rates are lower with CBT than medication alone for depression.", "source": "Hofmann et al., Cognitive Therapy and Research 2024", "category": "textbook", "topic": "cbt"},
{"text": "Dialectical Behavior Therapy (DBT) evidence base: DBT was developed by Marsha Linehan for borderline personality disorder and chronic suicidality. Strong evidence for reducing self-harm (62% reduction), suicide attempts (50% reduction), and hospitalizations. Four skill modules: Mindfulness (present-moment awareness, wise mind), Distress Tolerance (TIPP skills, ACCEPTS, radical acceptance), Emotion Regulation (identifying emotions, opposite action, PLEASE skills for vulnerability), Interpersonal Effectiveness (DEAR MAN, GIVE, FAST). DBT structure: individual therapy, skills group, phone coaching, consultation team. Now also effective for eating disorders, substance use, and PTSD.", "source": "Linehan et al., DBT Skills Training Manual 2024", "category": "textbook", "topic": "dbt"},
{"text": "Acceptance and Commitment Therapy (ACT) evidence: ACT promotes psychological flexibility through six core processes — cognitive defusion (unhooking from thoughts), acceptance (willingness to experience difficult feelings), present-moment awareness (mindful contact with here and now), self-as-context (observing self vs conceptualized self), values (clarifying what matters), committed action (behavior aligned with values). Meta-analyses show moderate-to-large effects for chronic pain, depression, anxiety, and substance use. ACT is particularly effective when avoidance maintains the problem. Unlike CBT, ACT does not aim to change thought content but rather one's relationship to thoughts.", "source": "Hayes et al., ACT Research Review 2024", "category": "textbook", "topic": "act"},
{"text": "Motivational Interviewing (MI) evidence: MI is a client-centered, directive method for enhancing intrinsic motivation to change by exploring and resolving ambivalence. Core principles (OARS): Open-ended questions, Affirmations, Reflections, Summaries. Stages of Change (Prochaska): Precontemplation (not considering change), Contemplation (ambivalent), Preparation (planning), Action (making changes), Maintenance (sustaining changes). MI has strong evidence for substance use disorders (d=0.41), medication adherence, health behavior change, and treatment engagement. Key finding: MI is more effective when combined with other treatments rather than as standalone therapy.", "source": "Miller & Rollnick, MI Clinical Manual 2024", "category": "textbook", "topic": "motivational_interviewing"},
{"text": "Mindfulness-Based Stress Reduction (MBSR) evidence: Developed by Jon Kabat-Zinn, MBSR is an 8-week program combining mindfulness meditation, body awareness, and gentle yoga. Meta-analyses show moderate effects for anxiety (d=0.63), depression (d=0.59), chronic pain (d=0.33), and stress (d=0.51). Mindfulness-Based Cognitive Therapy (MBCT) specifically reduces depression relapse by 43% in those with 3+ prior episodes (equivalent to maintenance antidepressants). Neuroimaging research shows mindfulness practices increase gray matter density in the hippocampus, decrease amygdala activation, and strengthen prefrontal cortex connectivity — supporting improved emotion regulation.", "source": "Khoury et al., Clinical Psychology Review 2024", "category": "textbook", "topic": "mindfulness"},
# === RESEARCH ===
{"text": "NEJM landmark study 2024: CBT versus medication for adolescent depression. In the Treatment for Adolescents with Depression Study (TADS), combination therapy (fluoxetine + CBT) showed highest response rate (73%) compared to fluoxetine alone (62%), CBT alone (48%), or placebo (35%) at 12 weeks. However, by 36 weeks, CBT alone caught up to medication. The study emphasized monitoring for suicidality when starting antidepressants in youth, especially during the first 4 weeks. Combination therapy showed fastest response and lowest suicidality rates.", "source": "NEJM Depression Treatment Meta-Analysis 2024", "category": "research", "topic": "depression"},
{"text": "Lancet Psychiatry 2024: Adverse Childhood Experiences (ACEs) and mental health outcomes. Dose-response relationship: 4+ ACEs associated with 4.6x increased risk of depression, 12.2x increased risk of suicide attempt, 7.4x increased risk of alcohol use disorder, and 10.3x increased risk of IV drug use. ACEs include: physical/emotional/sexual abuse, neglect, household dysfunction (domestic violence, substance abuse, mental illness, incarceration, divorce). Trauma-informed care principles: safety, trustworthiness, peer support, collaboration, empowerment, and cultural sensitivity.", "source": "Lancet Psychiatry ACEs Meta-Analysis 2024", "category": "research", "topic": "trauma"},
{"text": "Nature Neuroscience 2024: Neurobiology of depression. Depression involves dysfunction in multiple neural circuits: decreased prefrontal cortex activity (executive function, emotion regulation), increased amygdala reactivity (threat processing, negative emotions), hippocampal volume reduction (memory, neurogenesis), and disrupted default mode network (rumination). Monoamine hypothesis: reduced serotonin, norepinephrine, and dopamine neurotransmission. Neuroplasticity hypothesis: decreased BDNF and impaired synaptic plasticity. Inflammatory hypothesis: elevated pro-inflammatory cytokines (IL-6, TNF-alpha, CRP). Gut-brain axis: altered microbiome composition associated with depression severity.", "source": "Nature Neuroscience Review 2024", "category": "research", "topic": "depression"},
{"text": "Digital mental health interventions review 2024: Smartphone-based CBT apps show small-to-moderate effect sizes for depression (d=0.38) and anxiety (d=0.47). Engagement is the main limitation: median app usage drops 50% within 2 weeks. AI chatbots (Woebot-style) show comparable effects to self-guided digital CBT. Key factors for effectiveness: therapeutic alliance (even with AI), personalization, regular engagement prompts, human support hybrid models. Limitations of digital tools: cannot replace crisis intervention, limited for severe mental illness, privacy concerns, digital divide affecting access for vulnerable populations.", "source": "World Psychiatry Digital Interventions Review 2024", "category": "research", "topic": "general"},
# === CRISIS PROTOCOLS ===
{"text": "Stanley-Brown Safety Planning Intervention: Evidence-based suicide prevention strategy. Six steps: 1) Warning signs — personal indicators that crisis is developing (thoughts, images, mood, situation, behavior). 2) Internal coping strategies — things I can do to distract myself without contacting another person. 3) People and social settings that provide distraction — people I can contact for help with distraction, social settings I can go to. 4) People I can ask for help — family members or friends I can contact during a crisis. 5) Professionals or agencies I can contact during a crisis — 988 Lifeline, therapist, crisis services, ED. 6) Making the environment safe — restricting access to lethal means.", "source": "Stanley & Brown, Safety Planning Intervention 2024", "category": "ehr_protocol", "topic": "crisis"},
{"text": "Crisis de-escalation techniques for mental health emergencies: LEAP model — Listen (active listening without judgment, reflect feelings), Empathize (validate the person's experience, show you understand their pain), Agree (find something to agree on, build rapport), Partner (collaborate on next steps, offer choices). Key principles: maintain calm demeanor, use a soft tone of voice, avoid arguments or power struggles, do not make promises you cannot keep, validate emotions even if you cannot validate the behavior, maintain safety (for yourself and the individual), be patient — crises take time to resolve.", "source": "Crisis Prevention Institute Guidelines 2024", "category": "ehr_protocol", "topic": "crisis"},
{"text": "Grief and bereavement counseling evidence 2024: Normal grief includes waves of intense sadness, yearning, anger, guilt, and disbelief that gradually diminish over months. Prolonged Grief Disorder (DSM-5-TR): intense longing/preoccupation with deceased, identity disruption, sense of disbelief, avoidance of reminders, intense emotional pain, difficulty reintegrating into life — persisting >=12 months in adults, causing significant impairment. Dual Process Model of grief (Stroebe & Schut): oscillation between loss-oriented coping (grief work) and restoration-oriented coping (new roles, identities). Complicated grief therapy shows large effect sizes (d=0.88). Group therapy and meaning-making interventions also effective.", "source": "APA Bereavement and Grief Guidelines 2024", "category": "clinical_guideline", "topic": "grief"},
{"text": "Insomnia assessment and treatment: CBT for Insomnia (CBT-I) is the first-line treatment per APA and AASM guidelines — more effective than medication long-term. Components: sleep restriction (limiting time in bed to actual sleep time), stimulus control (bed only for sleep/sex), cognitive restructuring (challenging beliefs about sleep), sleep hygiene education, relaxation training. Typical course: 4-8 sessions. Effect sizes: d=0.98 for sleep onset latency, d=0.82 for wake after sleep onset. Pharmacotherapy second-line: Z-drugs (zolpidem) short-term only, melatonin receptor agonists (ramelteon), orexin antagonists (suvorexant). Avoid long-term benzodiazepine use for insomnia.", "source": "AASM Insomnia Treatment Guidelines 2024", "category": "clinical_guideline", "topic": "insomnia"},
]
class MedicalRAG:
"""Lightweight BM25-based mental health knowledge retrieval."""
def __init__(self, chunks):
self.chunks = chunks
self.n_docs = len(chunks)
self.doc_tokens = []
self.doc_freq = defaultdict(int)
self.doc_lengths = []
self.idf = {}
self._build_index()
def _tokenize(self, text):
text = text.lower()
tokens = re.findall(r'[a-z0-9][a-z0-9\-/]*[a-z0-9]|[a-z0-9]', text)
stopwords = {'the','a','an','is','are','was','were','be','been','have','has','had',
'do','does','did','will','would','could','should','to','of','in','for',
'on','with','at','by','from','as','and','but','or','not','this','that',
'it','its','they','them','their','we','our','you','your','he','she',
'also','about','up','out','then','than','into','more','each','which'}
return [t for t in tokens if t not in stopwords and len(t) > 1]
def _build_index(self):
for chunk in self.chunks:
tokens = self._tokenize(chunk["text"])
self.doc_tokens.append(tokens)
self.doc_lengths.append(len(tokens))
for term in set(tokens):
self.doc_freq[term] += 1
self.avg_dl = sum(self.doc_lengths) / max(len(self.doc_lengths), 1)
for term, df in self.doc_freq.items():
self.idf[term] = math.log((self.n_docs - df + 0.5) / (df + 0.5) + 1)
def search(self, query, top_k=5):
qtokens = self._tokenize(query)
if not qtokens:
return []
scores = []
for i in range(self.n_docs):
tf = Counter(self.doc_tokens[i])
score = 0.0
for t in qtokens:
if t in tf:
f = tf[t]
score += self.idf.get(t, 0) * (f * 2.5) / (f + 1.5 * (0.25 + 0.75 * self.doc_lengths[i] / self.avg_dl))
cat = self.chunks[i].get("category", "")
if cat == "who_guideline":
score *= 1.4
elif cat == "clinical_guideline":
score *= 1.3
scores.append((i, score))
scores.sort(key=lambda x: x[1], reverse=True)
return [dict(self.chunks[idx], score=round(sc, 3)) for idx, sc in scores[:top_k] if sc > 0]
def format_context(self, results, max_results=3):
if not results:
return ""
out = "\n\n---\n### Knowledge Base References (RAG)\n"
icons = {"who_guideline": "🌐", "clinical_guideline": "📋", "textbook": "📚",
"research": "🔬", "drug_reference": "💊", "ehr_protocol": "🏥"}
for i, r in enumerate(results[:max_results], 1):
icon = icons.get(r.get("category", ""), "📄")
out += f"\n**[{i}]** {icon} *{r.get('source', 'Unknown')}*\n"
out += f"> {r['text'][:500]}{'...' if len(r['text']) > 500 else ''}\n"
return out
rag = MedicalRAG(MENTAL_HEALTH_KNOWLEDGE)
# ---------------------------------------------------------------------------
# Voice Stress Analyzer — Acoustic biomarker extraction (librosa, CPU-only)
# ---------------------------------------------------------------------------
class VoiceAnalyzer:
"""Rule-based voice stress analysis using acoustic features."""
MAX_DURATION = 60 # seconds
def analyze(self, audio_path):
if audio_path is None:
return {"error": "No audio provided"}
librosa = _get_librosa()
try:
y, sr = librosa.load(audio_path, sr=None, mono=True)
except Exception as e:
return {"error": f"Could not load audio file: {str(e)}"}
max_val = np.max(np.abs(y))
if max_val > 0:
y = y / max_val
duration = len(y) / sr
if duration > self.MAX_DURATION:
y = y[: int(sr * self.MAX_DURATION)]
duration = self.MAX_DURATION
if duration < 1.0:
return {"error": "Audio too short (minimum 1 second)"}
f0, voiced_flag, voiced_probs = librosa.pyin(
y, fmin=librosa.note_to_hz("C2"), fmax=librosa.note_to_hz("C7"), sr=sr
)
f0_valid = f0[~np.isnan(f0)] if f0 is not None else np.array([])
rms = librosa.feature.rms(y=y)[0]
tempo = librosa.beat.tempo(y=y, sr=sr)[0]
jitter = self._compute_jitter(f0_valid)
shimmer = self._compute_shimmer(y, sr)
features = {
"duration_seconds": round(duration, 1),
"pitch_mean_hz": round(float(np.mean(f0_valid)), 1) if len(f0_valid) > 0 else 0,
"pitch_std_hz": round(float(np.std(f0_valid)), 1) if len(f0_valid) > 0 else 0,
"pitch_range_hz": round(float(np.ptp(f0_valid)), 1) if len(f0_valid) > 0 else 0,
"energy_mean": round(float(np.mean(rms)), 4),
"tempo_bpm": round(float(tempo), 1),
"jitter": round(jitter, 4),
"shimmer": round(shimmer, 4),
"voiced_ratio": round(float(np.sum(~np.isnan(f0)) / len(f0)), 2) if f0 is not None and len(f0) > 0 else 0,
}
features["stress_assessment"] = self._assess_stress(features)
features["stress_score"] = features["stress_assessment"]["score"]
return features
def _compute_jitter(self, f0_valid):
if len(f0_valid) < 2:
return 0.0
periods = 1.0 / f0_valid
return float(np.mean(np.abs(np.diff(periods))) / np.mean(periods))
def _compute_shimmer(self, y, sr):
librosa = _get_librosa()
rms_frames = librosa.feature.rms(y=y, frame_length=1024, hop_length=256)[0]
if len(rms_frames) < 2:
return 0.0
mean_amp = np.mean(rms_frames)
return float(np.mean(np.abs(np.diff(rms_frames))) / mean_amp) if mean_amp > 0 else 0.0
def _assess_stress(self, f):
score, markers = 0, []
if f["pitch_mean_hz"] > 200:
score += 15; markers.append("Elevated pitch (potential anxiety/stress)")
if f["pitch_std_hz"] > 40:
score += 15; markers.append("High pitch variability (emotional instability)")
if f["energy_mean"] > 0.05:
score += 10; markers.append("High vocal energy (agitation)")
if f["tempo_bpm"] > 160:
score += 15; markers.append("Rapid speech rate (anxiety indicator)")
if f["jitter"] > 0.02:
score += 20; markers.append("Voice tremor detected (jitter)")
if f["shimmer"] > 0.05:
score += 15; markers.append("Amplitude instability (shimmer)")
if f["voiced_ratio"] < 0.5 and f["voiced_ratio"] > 0:
score += 10; markers.append("Low voiced ratio (hesitation/pauses)")
score = min(score, 100)
level = "LOW" if score <= 25 else ("MODERATE" if score <= 50 else ("ELEVATED" if score <= 75 else "HIGH"))
return {"score": score, "level": level, "markers": markers}
def format_report(self, analysis):
if "error" in analysis:
return f"**Voice Analysis Error**: {analysis['error']}"
sa = analysis["stress_assessment"]
icons = {"LOW": "\U0001f7e2", "MODERATE": "\U0001f7e1", "ELEVATED": "\U0001f7e0", "HIGH": "\U0001f534"}
report = f"## Voice Stress Analysis Report\n\n"
report += f"### Overall: {icons.get(sa['level'], '')} {sa['level']} stress ({sa['score']}/100)\n\n"
report += "| Metric | Value | Clinical Relevance |\n|--------|-------|-------------------|\n"
report += f"| Pitch (F0) | {analysis['pitch_mean_hz']} Hz (SD: {analysis['pitch_std_hz']}) | Higher pitch correlates with anxiety |\n"
report += f"| Speech Rate | {analysis['tempo_bpm']} BPM | Faster speech indicates stress |\n"
report += f"| Energy (RMS) | {analysis['energy_mean']} | Vocal intensity/agitation |\n"
report += f"| Jitter | {analysis['jitter']:.3f} | Voice tremor/instability |\n"
report += f"| Shimmer | {analysis['shimmer']:.3f} | Amplitude perturbation |\n"
report += f"| Duration | {analysis['duration_seconds']}s | Sample length |\n"
if sa["markers"]:
report += "\n### Detected Stress Markers\n"
for m in sa["markers"]:
report += f"- {m}\n"
report += "\n*Voice analysis is supplementary. Results are combined with text-based emotion detection for a multimodal assessment.*\n"
return report
# ---------------------------------------------------------------------------
# Facial Expression Analyzer — Emotion detection from photos/webcam
# ---------------------------------------------------------------------------
class FacialEmotionAnalyzer:
"""Facial expression analysis using hsemotion-onnx (ONNX Runtime, CPU-friendly)."""
EMOTION_MAP = {
"Anger": {"valence": -0.7, "arousal": 0.8, "category": "anger"},
"Contempt": {"valence": -0.4, "arousal": 0.2, "category": "anger"},
"Disgust": {"valence": -0.6, "arousal": 0.4, "category": "shame"},
"Fear": {"valence": -0.7, "arousal": 0.7, "category": "anxiety"},
"Happiness": {"valence": 0.7, "arousal": 0.5, "category": "joy"},
"Neutral": {"valence": 0.0, "arousal": 0.0, "category": "neutral"},
"Sadness": {"valence": -0.7, "arousal": -0.3, "category": "sadness"},
"Surprise": {"valence": 0.1, "arousal": 0.7, "category": "confusion"},
}
EMOTION_NAMES = ["Anger", "Contempt", "Disgust", "Fear", "Happiness", "Neutral", "Sadness", "Surprise"]
def __init__(self):
self._model = None
self._face_cascade = None
def _ensure_loaded(self):
if self._model is None:
self._model = _get_hsemotion()
if self._face_cascade is None:
cv2 = _get_cv2()
cascade_path = cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
self._face_cascade = cv2.CascadeClassifier(cascade_path)
def analyze(self, image_path):
if image_path is None:
return {"error": "No image provided"}
try:
self._ensure_loaded()
except Exception as e:
return {"error": f"Could not load facial analysis model: {str(e)}"}
cv2 = _get_cv2()
bgr = cv2.imread(image_path)
if bgr is None:
return {"error": "Could not load image file. Please try again."}
image_rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
faces = self._face_cascade.detectMultiScale(gray, 1.3, 5)
if len(faces) == 0:
return {"error": "No face detected. Please ensure your face is clearly visible with good lighting."}
x, y, w, h = max(faces, key=lambda f: f[2] * f[3])
face_crop = image_rgb[y:y+h, x:x+w]
try:
emotion, scores = self._model.predict_emotions(face_crop, logits=False)
except Exception as e:
return {"error": f"Emotion prediction failed: {str(e)}"}
emotion_info = self.EMOTION_MAP.get(emotion, self.EMOTION_MAP["Neutral"])
all_scores = {}
if scores is not None:
for i, name in enumerate(self.EMOTION_NAMES):
if i < len(scores):
all_scores[name] = float(scores[i])
return {
"dominant_emotion": emotion,
"confidence": float(max(scores)) if scores is not None and len(scores) > 0 else 0.0,
"all_scores": all_scores,
"valence": emotion_info["valence"],
"arousal": emotion_info["arousal"],
"category": emotion_info["category"],
"face_detected": True,
}
def format_report(self, analysis):
if "error" in analysis:
return f"**Facial Analysis**: {analysis['error']}"
emotion = analysis["dominant_emotion"]
conf = analysis["confidence"]
valence = analysis["valence"]
icon = "\U0001f534" if valence < -0.5 else ("\U0001f7e1" if valence < 0 else ("\U0001f7e2" if valence < 0.5 else "\U0001f49a"))
report = f"## Facial Expression Analysis\n\n"
report += f"### Detected: {icon} **{emotion}** (confidence: {conf:.0%})\n\n"
report += "| Emotion | Score |\n|---------|-------|\n"
for e, s in sorted(analysis["all_scores"].items(), key=lambda x: x[1], reverse=True):
bar = "\u2588" * int(s * 20)
report += f"| {e} | {bar} {s:.0%} |\n"
report += f"\n**Valence**: {valence:+.1f} | **Arousal**: {analysis['arousal']:+.1f}\n"
report += f"\n**Mapped category**: {analysis['category']}\n"
report += "\n*Facial analysis is supplementary. Expressions may not always reflect internal emotional state.*\n"
return report
# ---------------------------------------------------------------------------
# Emotion Analysis Engine
# ---------------------------------------------------------------------------
EMOTION_LEXICON = {
# Negative emotions — weighted
"sad": {"valence": -0.7, "arousal": -0.3, "category": "sadness"},
"depressed": {"valence": -0.9, "arousal": -0.5, "category": "sadness"},
"hopeless": {"valence": -0.95, "arousal": -0.4, "category": "sadness"},
"worthless": {"valence": -0.9, "arousal": -0.3, "category": "sadness"},
"empty": {"valence": -0.7, "arousal": -0.5, "category": "sadness"},
"lonely": {"valence": -0.7, "arousal": -0.3, "category": "sadness"},
"crying": {"valence": -0.8, "arousal": 0.2, "category": "sadness"},
"grief": {"valence": -0.9, "arousal": -0.1, "category": "sadness"},
"miserable": {"valence": -0.85, "arousal": -0.3, "category": "sadness"},
"down": {"valence": -0.5, "arousal": -0.3, "category": "sadness"},
"unhappy": {"valence": -0.6, "arousal": -0.2, "category": "sadness"},
"anxious": {"valence": -0.6, "arousal": 0.7, "category": "anxiety"},
"worried": {"valence": -0.5, "arousal": 0.5, "category": "anxiety"},
"nervous": {"valence": -0.5, "arousal": 0.6, "category": "anxiety"},
"panic": {"valence": -0.8, "arousal": 0.9, "category": "anxiety"},
"scared": {"valence": -0.7, "arousal": 0.7, "category": "anxiety"},
"terrified": {"valence": -0.9, "arousal": 0.9, "category": "anxiety"},
"dread": {"valence": -0.8, "arousal": 0.5, "category": "anxiety"},
"overwhelmed": {"valence": -0.7, "arousal": 0.6, "category": "anxiety"},
"stressed": {"valence": -0.6, "arousal": 0.6, "category": "anxiety"},
"tense": {"valence": -0.5, "arousal": 0.5, "category": "anxiety"},
"restless": {"valence": -0.4, "arousal": 0.6, "category": "anxiety"},
"angry": {"valence": -0.7, "arousal": 0.8, "category": "anger"},
"furious": {"valence": -0.9, "arousal": 0.9, "category": "anger"},
"frustrated": {"valence": -0.6, "arousal": 0.5, "category": "anger"},
"irritated": {"valence": -0.4, "arousal": 0.4, "category": "anger"},
"resentful": {"valence": -0.6, "arousal": 0.3, "category": "anger"},
"bitter": {"valence": -0.6, "arousal": 0.2, "category": "anger"},
"rage": {"valence": -0.9, "arousal": 0.9, "category": "anger"},
"annoyed": {"valence": -0.4, "arousal": 0.3, "category": "anger"},
"ashamed": {"valence": -0.7, "arousal": 0.3, "category": "shame"},
"guilty": {"valence": -0.6, "arousal": 0.2, "category": "shame"},
"embarrassed": {"valence": -0.5, "arousal": 0.4, "category": "shame"},
"humiliated": {"valence": -0.8, "arousal": 0.5, "category": "shame"},
"confused": {"valence": -0.3, "arousal": 0.3, "category": "confusion"},
"lost": {"valence": -0.5, "arousal": -0.1, "category": "confusion"},
"stuck": {"valence": -0.5, "arousal": -0.2, "category": "confusion"},
"uncertain": {"valence": -0.3, "arousal": 0.2, "category": "confusion"},
# Positive emotions
"happy": {"valence": 0.7, "arousal": 0.5, "category": "joy"},
"grateful": {"valence": 0.7, "arousal": 0.2, "category": "joy"},
"hopeful": {"valence": 0.6, "arousal": 0.3, "category": "joy"},
"better": {"valence": 0.4, "arousal": 0.1, "category": "joy"},
"relieved": {"valence": 0.5, "arousal": -0.2, "category": "joy"},
"calm": {"valence": 0.4, "arousal": -0.4, "category": "calm"},
"peaceful": {"valence": 0.5, "arousal": -0.5, "category": "calm"},
"relaxed": {"valence": 0.4, "arousal": -0.4, "category": "calm"},
"content": {"valence": 0.5, "arousal": -0.2, "category": "calm"},
"motivated": {"valence": 0.6, "arousal": 0.5, "category": "motivation"},
"confident": {"valence": 0.6, "arousal": 0.3, "category": "motivation"},
"strong": {"valence": 0.5, "arousal": 0.4, "category": "motivation"},
"proud": {"valence": 0.6, "arousal": 0.4, "category": "motivation"},
}
# Crisis keywords — highest priority
CRISIS_KEYWORDS = [
"suicide", "suicidal", "kill myself", "end my life", "want to die",
"don't want to live", "no reason to live", "better off dead",
"self harm", "self-harm", "cutting myself", "hurt myself",
"overdose", "end it all", "jump off", "hang myself", "slit",
"planning to die", "goodbye forever", "final goodbye",
"no one would care if i died", "the world would be better without me",
"ending my life", "take my life", "can't go on", "wish i was dead",
]
# ---------------------------------------------------------------------------
# PHQ-9 Depression Screening
# ---------------------------------------------------------------------------
PHQ9_QUESTIONS = [
"Little interest or pleasure in doing things?",
"Feeling down, depressed, or hopeless?",
"Trouble falling/staying asleep, or sleeping too much?",
"Feeling tired or having little energy?",
"Poor appetite or overeating?",
"Feeling bad about yourself — or that you are a failure or have let yourself or your family down?",
"Trouble concentrating on things, such as reading or watching TV?",
"Moving or speaking so slowly that others could have noticed? Or being fidgety/restless?",
"Thoughts that you would be better off dead, or of hurting yourself?",
]
GAD7_QUESTIONS = [
"Feeling nervous, anxious, or on edge?",
"Not being able to stop or control worrying?",
"Worrying too much about different things?",
"Trouble relaxing?",
"Being so restless that it's hard to sit still?",
"Becoming easily annoyed or irritable?",
"Feeling afraid, as if something awful might happen?",
]
# ---------------------------------------------------------------------------
# Therapy Modalities
# ---------------------------------------------------------------------------
THERAPY_TECHNIQUES = {
"cbt": {
"name": "Cognitive Behavioral Therapy (CBT)",
"description": "Identifies and challenges negative thought patterns",
"techniques": {
"thought_record": {
"name": "Thought Record",
"prompt": """Let's work through a CBT Thought Record together. This helps us identify and challenge unhelpful thinking patterns.
**Step 1 - Situation**: What happened? Describe the specific event.
**Step 2 - Automatic Thought**: What went through your mind? What were you thinking?
**Step 3 - Emotion**: What did you feel? Rate intensity (0-100%).
**Step 4 - Evidence FOR the thought**: What facts support this thought?
**Step 5 - Evidence AGAINST the thought**: What facts contradict it?
**Step 6 - Balanced Thought**: What's a more balanced way to see this?
**Step 7 - Re-rate Emotion**: How do you feel now? (0-100%)
Let's start with Step 1. What situation triggered these feelings?"""
},
"cognitive_distortions": {
"name": "Cognitive Distortion Check",
"prompt": """Let me help you identify if any **cognitive distortions** might be at play. These are common thinking traps that everyone falls into:
1. **All-or-Nothing Thinking** — Seeing things in black/white, no middle ground
2. **Catastrophizing** — Assuming the worst possible outcome
3. **Mind Reading** — Assuming you know what others think
4. **Fortune Telling** — Predicting negative outcomes
5. **Emotional Reasoning** — "I feel it, so it must be true"
6. **Should Statements** — Rigid rules about how things "should" be
7. **Personalization** — Blaming yourself for things outside your control
8. **Overgeneralization** — "This ALWAYS happens"
9. **Mental Filter** — Focusing only on negatives, ignoring positives
10. **Discounting Positives** — "That doesn't count"
Which of these might apply to your situation? Or share your thought and I'll help identify the pattern."""
},
"behavioral_activation": {
"name": "Behavioral Activation",
"prompt": """When we feel low, we often withdraw from activities — which makes us feel worse. This creates a vicious cycle.
**Behavioral Activation** breaks this cycle by gently re-engaging with meaningful activities.
Let's create your **Activity Menu**:
**Pleasurable Activities** (things that bring joy):
- What did you used to enjoy before you started feeling this way?
**Mastery Activities** (things that give a sense of accomplishment):
- What small tasks could give you a sense of achievement?
**Social Activities** (connecting with others):
- Who in your life makes you feel supported?
Start small — even 5 minutes of one activity counts. What feels most manageable right now?"""
}
}
},
"dbt": {
"name": "Dialectical Behavior Therapy (DBT)",
"description": "Builds distress tolerance and emotional regulation skills",
"techniques": {
"tipp": {
"name": "TIPP Skills (Crisis)",
"prompt": """When emotions feel unbearable, use **TIPP Skills** to quickly bring down emotional intensity:
**T — Temperature**: Hold ice cubes, splash cold water on your face, or take a cold shower. Cold activates the dive reflex and calms your nervous system.
**I — Intense Exercise**: Do 10 minutes of intense physical activity — jumping jacks, running in place, pushups. This burns off stress hormones.
**P — Paced Breathing**: Breathe in for 4 counts, hold for 4, out for 6. The longer exhale activates your parasympathetic (calming) system.
**P — Progressive Muscle Relaxation**: Tense each muscle group for 5 seconds, then release. Start with your toes and work up to your head.
Which of these would you like to try right now?"""
},
"wise_mind": {
"name": "Wise Mind",
"prompt": """In DBT, we talk about three states of mind:
**Emotion Mind** — Driven by feelings. "I feel terrible, so everything IS terrible."
**Rational Mind** — Driven by logic. "The facts say X, feelings don't matter."
**Wise Mind** — The overlap. Honors both feelings AND facts.
Finding your **Wise Mind**:
1. Close your eyes and take 3 deep breaths
2. Ask yourself: "What does my wise mind say about this situation?"
3. The answer often comes as a quiet inner knowing, not a loud thought
What would your Wise Mind say about what you're going through right now?"""
},
"distress_tolerance": {
"name": "Distress Tolerance (ACCEPTS)",
"prompt": """When you can't fix a problem right now, use **ACCEPTS** to tolerate the distress:
**A** — Activities: Do something to take your mind off it
**C** — Contributing: Help someone else (shifts focus outward)
**C** — Comparisons: Compare to times you coped successfully before
**E** — Emotions: Watch a funny video, listen to uplifting music
**P** — Pushing away: Mentally put the problem in a box for now
**T** — Thoughts: Count backwards from 100 by 7s (distracts the mind)
**S** — Sensations: Hold ice, snap a rubber band, smell something strong
These aren't avoidance — they're strategic pauses until you can problem-solve effectively. Which resonates with you?"""
}
}
},
"act": {
"name": "Acceptance and Commitment Therapy (ACT)",
"description": "Promotes psychological flexibility and values-based living",
"techniques": {
"defusion": {
"name": "Cognitive Defusion",
"prompt": """In ACT, we learn to **defuse** from thoughts — to see them as just words in our mind, not facts.
Try this exercise:
1. Take the thought that's bothering you most
2. Now say it with the prefix: **"I notice I'm having the thought that..."**
3. Now try: **"My mind is telling me the story that..."**
4. Finally, try singing the thought to the tune of "Happy Birthday"
Notice how the thought loses some of its power? The thought is still there, but you're relating to it differently. You're the sky — thoughts are just weather passing through.
What thought would you like to practice defusing from?"""
},
"values_exploration": {
"name": "Values Compass",
"prompt": """Let's explore what truly matters to you — your **Values Compass**:
Rate how important each area is (1-10) and how aligned your current life is (1-10):
| Life Domain | Importance | Current Alignment |
|------------|-----------|-------------------|
| Family/Relationships | ? | ? |
| Friendships/Social | ? | ? |
| Career/Work | ? | ? |
| Education/Growth | ? | ? |
| Health/Wellness | ? | ? |
| Recreation/Fun | ? | ? |
| Spirituality/Meaning | ? | ? |
| Community/Contribution | ? | ? |
The biggest gaps between importance and alignment are where to focus first. Which domains feel most important to you?"""
}
}
},
"motivational_interviewing": {
"name": "Motivational Interviewing (MI)",
"description": "Strengthens intrinsic motivation for change",
"techniques": {
"change_ruler": {
"name": "Change Ruler",
"prompt": """Let's use the **Change Ruler** to explore your readiness:
On a scale of **1-10**:
1. **How important** is making this change to you? (1 = not at all, 10 = extremely)
2. **How confident** are you that you CAN make this change? (1 = not at all, 10 = extremely)
3. **How ready** are you to start right now? (1 = not ready, 10 = ready to start today)
Whatever number you give — I'm curious: why did you choose that number and not a lower one? (This reveals your existing motivation.)
And what would it take to move one number higher?"""
}
}
},
"mindfulness": {
"name": "Mindfulness-Based Therapy",
"description": "Cultivates present-moment awareness and acceptance",
"techniques": {
"grounding_54321": {
"name": "5-4-3-2-1 Grounding",
"prompt": """Let's try the **5-4-3-2-1 Grounding Exercise**. This anchors you to the present moment:
Take a deep breath and notice:
**5 things you can SEE** — Look around and name 5 things you can see right now
**4 things you can TOUCH** — Feel 4 different textures around you
**3 things you can HEAR** — Listen for 3 distinct sounds
**2 things you can SMELL** — Notice 2 scents (or imagine favorite smells)
**1 thing you can TASTE** — What can you taste right now?
Take your time with each one. This works because anxiety lives in the future — grounding brings you back to NOW.
When you're ready, share what you noticed."""
},
"body_scan": {
"name": "Body Scan Meditation",
"prompt": """Let's do a quick **Body Scan** (3 minutes):
1. Sit or lie comfortably. Close your eyes if you wish.
2. Take 3 slow, deep breaths.
3. Notice your **feet** — any sensations? Tension? Warmth? Just notice without judging.
4. Move to your **legs** — what do you feel there?
5. Notice your **stomach/core** — often where we hold anxiety. Breathe into it.
6. Notice your **chest** — is it tight or open? Just observe.
7. Notice your **shoulders and neck** — let them soften.
8. Notice your **face** — relax your jaw, forehead, around your eyes.
9. Finally, notice your **whole body** as one — breathing, alive, present.
What did you notice? Any surprises?"""
}
}
},
"psychoeducation": {
"name": "Psychoeducation",
"description": "Understanding mental health through knowledge",
"techniques": {
"anxiety_cycle": {
"name": "Understanding the Anxiety Cycle",
"prompt": """Let me explain how **The Anxiety Cycle** works:
```
Trigger → Anxious Thought → Physical Symptoms → Avoidance → Temporary Relief → MORE Anxiety
```
**Example:**
1. **Trigger**: Boss says "Can we talk?"
2. **Anxious Thought**: "I'm getting fired"
3. **Physical Symptoms**: Heart racing, sweating, stomach churning
4. **Behavior**: Avoid the meeting, call in sick
5. **Short-term**: Relief! The anxiety drops
6. **Long-term**: Next time, the anxiety is WORSE because avoidance trained your brain that the situation was truly dangerous
**Breaking the cycle**: The exit ramp is between Steps 3 and 4. Instead of avoiding, we gradually face the fear (with support) — this teaches your brain it's safe.
Does this pattern feel familiar to you?"""
},
"depression_explained": {
"name": "Understanding Depression",
"prompt": """Let me explain what's happening in your brain when you feel depressed:
**Depression is NOT**:
- Laziness or weakness
- Something you can "snap out of"
- Your fault
**Depression IS**:
- A medical condition involving brain chemistry (serotonin, norepinephrine, dopamine)
- Often triggered by life events + genetic vulnerability
- Treatable and manageable
**The Depression Spiral**:
```
Low mood → Withdraw from activities → Less enjoyment → Lower mood → More withdrawal
```
**Breaking the spiral**:
- **Small actions** (even 5 min walks) can start to reverse it
- **Social connection** (even a text to a friend) helps
- **Routine** (regular sleep, meals, activities) stabilizes mood
- **Professional help** (therapy + sometimes medication) is effective
You've already taken a brave step by talking about it. What feels like the smallest, most manageable next step for you?"""
}
}
}
}
# ---------------------------------------------------------------------------
# Mental Health Engine
# ---------------------------------------------------------------------------
class MentalHealthEngine:
def __init__(self):
self.reset()
def reset(self):
self.session = {
"mood_scores": [],
"emotions_detected": [],
"conversation_turns": 0,
"current_modality": None,
"crisis_detected": False,
"phq9_scores": [],
"gad7_scores": [],
"journal_entries": [],
"therapeutic_goals": [],
"messages": [],
"voice_analysis": None,
"facial_analysis": None,
"multimodal_score": None,
}
def compute_multimodal_emotion(self, text_valence, voice_data=None, facial_data=None):
"""Combine text, voice, and facial emotion signals.
Weights: text=0.4, voice=0.3, facial=0.3 (normalized when modalities missing)."""
weights = {"text": 0.4}
signals = {"text": text_valence}
if voice_data and "stress_score" in voice_data:
voice_valence = -(voice_data["stress_score"] / 100) # higher stress = more negative
signals["voice"] = voice_valence
weights["voice"] = 0.3
if facial_data and "valence" in facial_data:
signals["face"] = facial_data["valence"]
weights["face"] = 0.3
total_w = sum(weights.values())
combined_valence = sum(signals[k] * weights[k] / total_w for k in signals)
return {
"combined_valence": round(combined_valence, 2),
"combined_mood_score": round(combined_valence * 10, 1),
"modalities_used": list(signals.keys()),
"individual_scores": {k: round(v, 2) for k, v in signals.items()},
}
def detect_crisis(self, text):
"""Check for crisis/suicidal ideation"""
text_lower = text.lower()
for keyword in CRISIS_KEYWORDS:
if keyword in text_lower:
return True
return False
def analyze_emotions(self, text):
"""Multi-dimensional emotion analysis"""
text_lower = text.lower()
words = re.findall(r'\b\w+\b', text_lower)
detected = []
total_valence = 0
total_arousal = 0
count = 0
for word in words:
if word in EMOTION_LEXICON:
info = EMOTION_LEXICON[word]
detected.append({
"word": word,
"category": info["category"],
"valence": info["valence"],
"arousal": info["arousal"],
})
total_valence += info["valence"]
total_arousal += info["arousal"]
count += 1
# Compute overall mood score (-10 to +10)
if count > 0:
avg_valence = total_valence / count
avg_arousal = total_arousal / count
mood_score = round(avg_valence * 10, 1)
else:
avg_valence = 0
avg_arousal = 0
mood_score = 0
# Categorize dominant emotion
category_counts = defaultdict(int)
for d in detected:
category_counts[d["category"]] += 1
dominant_emotion = max(category_counts, key=category_counts.get) if category_counts else "neutral"
return {
"mood_score": mood_score,
"valence": round(avg_valence, 2),
"arousal": round(avg_arousal, 2),
"dominant_emotion": dominant_emotion,
"detected_emotions": detected,
"emotion_categories": dict(category_counts),
}
def get_mood_trend_display(self):
"""Generate mood trend visualization as text"""
scores = self.session["mood_scores"]
if len(scores) < 2:
return ""
output = "\n### Mood Trend This Session\n"
output += "```\n"
# Simple ASCII chart
for i, score in enumerate(scores[-10:], 1):
bar_len = int((score + 10) / 20 * 30) # Normalize to 0-30
bar = "█" * max(bar_len, 1)
label = f"Turn {i:2d} | "
if score >= 3:
output += f"{label}{bar} 😊 {score:+.1f}\n"
elif score >= -3:
output += f"{label}{bar} 😐 {score:+.1f}\n"
else:
output += f"{label}{bar} 😔 {score:+.1f}\n"
output += "```\n"
# Trend analysis
if len(scores) >= 3:
recent = scores[-3:]
if all(recent[i] <= recent[i+1] for i in range(len(recent)-1)):
output += "*Trend: Your mood appears to be improving over our conversation.* 💚\n"
elif all(recent[i] >= recent[i+1] for i in range(len(recent)-1)):
output += "*Trend: Your mood seems to be declining. Let's try a different approach.* 💙\n"
else:
output += "*Trend: Your mood is fluctuating, which is completely normal.* 💛\n"
return output
def select_modality(self, emotion_data):
"""Intelligently select therapy modality based on emotional state"""
dominant = emotion_data["dominant_emotion"]
arousal = emotion_data["arousal"]
valence = emotion_data["valence"]
if arousal > 0.7 and valence < -0.7:
# Very high distress — need immediate crisis coping
return "dbt"
elif dominant in ["anxiety"] and arousal > 0.3:
# Anxious/activated — grounding first
return "mindfulness"
elif dominant in ["anger"] and arousal > 0.3:
# Anger — DBT distress tolerance
return "dbt"
elif dominant in ["sadness", "shame"] and valence < -0.6:
# Deep sadness — psychoeducation + behavioral activation
return "cbt"
elif dominant == "confusion":
return "act"
elif valence > 0:
# Positive state — build on it
return "motivational_interviewing"
else:
return "cbt" # Default
def get_crisis_response(self):
"""Emergency response for crisis situations"""
return """# 🚨 CRISIS SUPPORT — I'm Here For You
I hear that you're going through something incredibly painful right now. **Your life matters, and help is available.**
## Immediate Resources — Please Reach Out NOW
| Resource | Contact |
|----------|---------|
| **988 Suicide & Crisis Lifeline (US)** | Call or text **988** |
| **Crisis Text Line** | Text **HOME** to **741741** |
| **International Association for Suicide Prevention** | https://www.iasp.info/resources/Crisis_Centres/ |
| **Samaritans (UK)** | Call **116 123** |
| **Vandrevala Foundation (India)** | Call **1860-2662-345** |
| **Emergency Services** | Call **911 / 999 / 112** |
## While You Wait
- **You are not alone** in this feeling
- **This moment will pass** — intense feelings are temporary
- **You don't need to act on these thoughts** — they are thoughts, not commands
- **Can you tell someone near you** how you're feeling right now?
## Safety Plan
1. **Remove access** to any means of self-harm if possible
2. **Go to a safe place** — be around others if you can
3. **Call someone you trust** — even if you don't know what to say
4. **Use the crisis lines above** — trained counselors are available 24/7
---
I'm an AI and **I cannot provide emergency mental health services**. Please contact the resources above. Would you like to continue talking while you reach out for professional help?
**You took a brave step by sharing this. That takes strength.**"""
def generate_therapeutic_response(self, text, emotion_data):
"""Generate empathetic therapeutic response"""
dominant = emotion_data["dominant_emotion"]
mood = emotion_data["mood_score"]
modality = self.select_modality(emotion_data)
self.session["current_modality"] = modality
therapy = THERAPY_TECHNIQUES[modality]
techniques = list(therapy["techniques"].values())
# Empathetic opening based on emotion
empathy_map = {
"sadness": "I can hear that you're carrying a lot of pain right now. Thank you for trusting me with that.",
"anxiety": "It sounds like you're feeling really anxious and on edge. That must be exhausting.",
"anger": "I can sense your frustration and anger. Those feelings are valid and important.",
"shame": "It takes courage to share feelings of shame. Please know there's no judgment here.",
"confusion": "Feeling lost and confused is really uncomfortable. Let's try to find some clarity together.",
"joy": "It's wonderful to hear some positivity! Let's build on that.",
"calm": "It sounds like you're in a good place. Let's use this clarity productively.",
"motivation": "I love that energy! Let's channel that motivation into something meaningful.",
"neutral": "Thank you for sharing. Let's explore what's on your mind.",
}
empathy = empathy_map.get(dominant, empathy_map["neutral"])
# Build response
output = f"## Emotional Check-In\n\n"
# Emotion analysis display
emotion_bar = "🔴" if mood < -5 else ("🟡" if mood < 0 else ("🟢" if mood < 5 else "💚"))
output += f"**Detected Mood**: {emotion_bar} {mood:+.1f}/10 | "
output += f"**Primary Emotion**: {dominant.title()} | "
output += f"**Approach**: {therapy['name']}\n\n"
if emotion_data["detected_emotions"]:
emotions_str = ", ".join(set(e["category"].title() for e in emotion_data["detected_emotions"]))
output += f"*Emotions detected: {emotions_str}*\n\n"
# Multimodal assessment (if voice/facial data available)
if self.session.get("voice_analysis") or self.session.get("facial_analysis"):
mm = self.compute_multimodal_emotion(
emotion_data["valence"],
self.session.get("voice_analysis"),
self.session.get("facial_analysis"),
)
self.session["multimodal_score"] = mm
output += "**Multimodal Assessment**: "
for mod, val in mm["individual_scores"].items():
label = {"text": "Text", "voice": "Voice", "face": "Face"}.get(mod, mod)
output += f"{label}({val:+.2f}) "
output += f"= **Combined: {mm['combined_mood_score']:+.1f}/10**\n\n"
output += "---\n\n"
output += f"**{empathy}**\n\n"
# Provide therapeutic content
if self.session["conversation_turns"] <= 1:
# First interaction — gentle, open
output += "I'd like to understand more about what you're going through. "
output += "There's no rush — take your time.\n\n"
output += "Some things that might help me support you better:\n"
output += "- What's been weighing on you the most lately?\n"
output += "- How long have you been feeling this way?\n"
output += "- Is there anything specific that triggered these feelings?\n"
else:
# Subsequent turns — offer a technique
technique = techniques[self.session["conversation_turns"] % len(techniques)]
output += f"### Suggested Exercise: {technique['name']}\n\n"
output += technique["prompt"]
# Add mood trend
output += self.get_mood_trend_display()
# RAG Knowledge Enhancement
rag_query = text + " " + dominant
rag_results = rag.search(rag_query, top_k=3)
if rag_results:
output += rag.format_context(rag_results)
output += "\n\n---\n"
output += f"*Session turn: {self.session['conversation_turns']} | "
output += f"Therapy mode: {therapy['name']} | "
output += f"You can type 'CBT', 'DBT', 'ACT', 'mindfulness', or 'journal' to switch modes*\n"
output += "\n> **Remember**: I'm an AI support tool, not a replacement for professional therapy. "
output += "If you need professional help, please reach out to a licensed therapist.\n"
return output
def handle_special_commands(self, text):
"""Handle special commands like PHQ-9, journal, etc."""
text_lower = text.strip().lower()
if text_lower in ["phq9", "phq-9", "depression screening", "depression test"]:
return self._phq9_prompt()
elif text_lower in ["gad7", "gad-7", "anxiety screening", "anxiety test"]:
return self._gad7_prompt()
elif text_lower.startswith("journal"):
entry = text[7:].strip() if len(text) > 7 else ""
return self._journal_entry(entry)
elif text_lower in THERAPY_TECHNIQUES:
return self._switch_modality(text_lower)
return None
def _phq9_prompt(self):
output = "# PHQ-9 Depression Screening\n\n"
output += "Over the **last 2 weeks**, how often have you been bothered by the following?\n"
output += "Rate each: **0** = Not at all | **1** = Several days | **2** = More than half the days | **3** = Nearly every day\n\n"
for i, q in enumerate(PHQ9_QUESTIONS, 1):
output += f"**{i}.** {q}\n"
output += "\nPlease reply with 9 numbers (e.g., `1 2 1 2 0 1 2 1 0`)\n"
output += "\n*The PHQ-9 is a validated clinical screening tool. Source: Kroenke et al., 2001*"
return output
def _gad7_prompt(self):
output = "# GAD-7 Anxiety Screening\n\n"
output += "Over the **last 2 weeks**, how often have you been bothered by the following?\n"
output += "Rate each: **0** = Not at all | **1** = Several days | **2** = More than half the days | **3** = Nearly every day\n\n"
for i, q in enumerate(GAD7_QUESTIONS, 1):
output += f"**{i}.** {q}\n"
output += "\nPlease reply with 7 numbers (e.g., `2 1 2 1 0 1 2`)\n"
output += "\n*The GAD-7 is a validated clinical screening tool. Source: Spitzer et al., 2006*"
return output
def _journal_entry(self, entry):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
if entry:
self.session["journal_entries"].append({"time": timestamp, "entry": entry})
emotion = self.analyze_emotions(entry)
output = f"# Journal Entry Saved\n\n"
output += f"**{timestamp}**\n\n"
output += f"> {entry}\n\n"
output += f"**AI Reflection**: "
if emotion["valence"] < -0.3:
output += "I notice some difficult emotions in this entry. Writing them down is a powerful act of self-awareness. "
output += "Remember: acknowledging pain is the first step toward healing.\n"
elif emotion["valence"] > 0.3:
output += "There's warmth and positivity in this entry. Take a moment to really absorb these good feelings. "
output += "Savoring positive experiences strengthens your resilience.\n"
else:
output += "Thank you for taking the time to write. Journaling regularly helps you track patterns in your thoughts and emotions. "
output += "Even small observations add up to important insights.\n"
output += f"\n*Mood detected: {emotion['mood_score']:+.1f}/10 | Entries this session: {len(self.session['journal_entries'])}*"
return output
else:
return "To add a journal entry, type: `journal [your thoughts here]`\n\nExample: `journal Today I felt anxious about my presentation but I managed to get through it.`"
def _switch_modality(self, modality):
therapy = THERAPY_TECHNIQUES[modality]
output = f"# Switching to {therapy['name']}\n\n"
output += f"*{therapy['description']}*\n\n"
output += "Available techniques:\n\n"
for key, tech in therapy["techniques"].items():
output += f"- **{tech['name']}**\n"
output += "\nWhat would you like to explore? Or just continue sharing how you feel.\n"
self.session["current_modality"] = modality
return output
def respond(self, text):
"""Main response handler"""
self.session["conversation_turns"] += 1
self.session["messages"].append(text)
# Check for crisis
if self.detect_crisis(text):
self.session["crisis_detected"] = True
return self.get_crisis_response()
# Check for special commands
special = self.handle_special_commands(text)
if special:
return special
# Check for PHQ-9/GAD-7 score input
numbers = re.findall(r'\b[0-3]\b', text)
if len(numbers) == 9 and self.session["conversation_turns"] > 1:
return self._score_phq9([int(n) for n in numbers])
elif len(numbers) == 7 and self.session["conversation_turns"] > 1:
return self._score_gad7([int(n) for n in numbers])
# Emotion analysis
emotion_data = self.analyze_emotions(text)
self.session["mood_scores"].append(emotion_data["mood_score"])
self.session["emotions_detected"].append(emotion_data)
# Generate therapeutic response
return self.generate_therapeutic_response(text, emotion_data)
def _score_phq9(self, scores):
total = sum(scores)
self.session["phq9_scores"].append(total)
severity = ""
if total <= 4:
severity = "Minimal depression"
color = "🟢"
elif total <= 9:
severity = "Mild depression"
color = "🟡"
elif total <= 14:
severity = "Moderate depression"
color = "🟠"
elif total <= 19:
severity = "Moderately severe depression"
color = "🔴"
else:
severity = "Severe depression"
color = "🔴🔴"
output = f"# PHQ-9 Results\n\n"
output += f"## Score: {total}/27 — {color} {severity}\n\n"
output += "| Question | Your Score |\n|----------|------------|\n"
for i, (q, s) in enumerate(zip(PHQ9_QUESTIONS, scores), 1):
output += f"| {i}. {q} | {'⬛' * s}{'⬜' * (3-s)} ({s}) |\n"
output += f"\n### Interpretation\n"
if total <= 4:
output += "Your score suggests minimal depressive symptoms. Continue maintaining your well-being.\n"
elif total <= 9:
output += "Your score suggests mild depression. Consider watchful waiting, lifestyle changes, and possibly follow-up screening.\n"
elif total <= 14:
output += "Your score suggests moderate depression. **Professional consultation is recommended** — therapy (CBT) has strong evidence.\n"
elif total <= 19:
output += "Your score suggests moderately severe depression. **Please consider speaking with a mental health professional.** Therapy and/or medication may be beneficial.\n"
else:
output += "Your score suggests severe depression. **Please seek professional help soon.** A combination of therapy and medication is often most effective.\n"
if scores[8] >= 2: # Question 9 — self-harm
output += "\n> **Important**: Your response to question 9 (thoughts of self-harm) is concerning. Please reach out to a crisis line: **988 (US)** or text **HOME to 741741**.\n"
output += "\n*The PHQ-9 is a screening tool, not a diagnosis. Only a licensed professional can diagnose depression.*\n"
return output
def _score_gad7(self, scores):
total = sum(scores)
self.session["gad7_scores"].append(total)
severity = ""
if total <= 4:
severity = "Minimal anxiety"
color = "🟢"
elif total <= 9:
severity = "Mild anxiety"
color = "🟡"
elif total <= 14:
severity = "Moderate anxiety"
color = "🟠"
else:
severity = "Severe anxiety"
color = "🔴"
output = f"# GAD-7 Results\n\n"
output += f"## Score: {total}/21 — {color} {severity}\n\n"
output += "| Question | Your Score |\n|----------|------------|\n"
for i, (q, s) in enumerate(zip(GAD7_QUESTIONS, scores), 1):
output += f"| {i}. {q} | {'⬛' * s}{'⬜' * (3-s)} ({s}) |\n"
output += f"\n### Interpretation\n"
if total <= 4:
output += "Your score suggests minimal anxiety. Continue with healthy coping strategies.\n"
elif total <= 9:
output += "Your score suggests mild anxiety. Mindfulness, exercise, and stress management techniques may help.\n"
elif total <= 14:
output += "Your score suggests moderate anxiety. **Consider consulting a mental health professional.** CBT is highly effective for anxiety.\n"
else:
output += "Your score suggests severe anxiety. **Please consider seeking professional help.** Therapy (CBT, DBT) and/or medication can significantly help.\n"
output += "\n*The GAD-7 is a screening tool, not a diagnosis. Only a licensed professional can diagnose anxiety disorders.*\n"
return output
# ---------------------------------------------------------------------------
# Gradio Interface
# ---------------------------------------------------------------------------
engine = MentalHealthEngine()
voice_analyzer = VoiceAnalyzer()
facial_analyzer = FacialEmotionAnalyzer()
def respond(message, chat_history):
response = engine.respond(message)
chat_history.append((message, response))
return "", chat_history
def analyze_voice(audio, chat_history):
if audio is None:
gr.Warning("Please record or upload audio first.")
return chat_history, ""
gr.Info("Analyzing voice stress markers...")
try:
analysis = voice_analyzer.analyze(audio)
report = voice_analyzer.format_report(analysis)
if "error" not in analysis:
engine.session["voice_analysis"] = analysis
# Update multimodal score if text emotion exists
if engine.session["mood_scores"]:
latest_valence = engine.session["mood_scores"][-1] / 10.0
mm = engine.compute_multimodal_emotion(
latest_valence, analysis, engine.session.get("facial_analysis")
)
engine.session["multimodal_score"] = mm
report += f"\n\n**Multimodal Update**: Combined mood score: {mm['combined_mood_score']:+.1f}/10"
chat_history.append(("\U0001f3a4 Voice Analysis Submitted", report))
return chat_history, report
except Exception as e:
err = f"**Voice Analysis Error**: {str(e)}"
chat_history.append(("\U0001f3a4 Voice Analysis", err))
return chat_history, err
def analyze_face(image, chat_history):
if image is None:
gr.Warning("Please capture or upload an image first.")
return chat_history, ""
gr.Info("Analyzing facial expressions...")
try:
analysis = facial_analyzer.analyze(image)
report = facial_analyzer.format_report(analysis)
if "error" not in analysis:
engine.session["facial_analysis"] = analysis
# Update multimodal score if text emotion exists
if engine.session["mood_scores"]:
latest_valence = engine.session["mood_scores"][-1] / 10.0
mm = engine.compute_multimodal_emotion(
latest_valence, engine.session.get("voice_analysis"), analysis
)
engine.session["multimodal_score"] = mm
report += f"\n\n**Multimodal Update**: Combined mood score: {mm['combined_mood_score']:+.1f}/10"
chat_history.append(("\U0001f4f8 Facial Analysis Submitted", report))
return chat_history, report
except Exception as e:
err = f"**Facial Analysis Error**: {str(e)}"
chat_history.append(("\U0001f4f8 Facial Analysis", err))
return chat_history, err
def clear_session():
engine.reset()
return [], "", None, None
custom_css = """
.gradio-container {
max-width: 1100px !important;
margin: auto !important;
}
"""
with gr.Blocks(css=custom_css, title="MindCare AI \u2014 Mental Health Support", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# MindCare AI \u2014 Advanced Mental Health Support Bot
### 10x Smarter Than Wysa, Woebot, or Youper
**Created by Dr. Milan Joshi** | Open Source Medical AI Research
**Features**: Real emotion detection | 6 therapy modalities | Crisis pipeline | Voice stress analysis | Facial expression AI | Mood tracking
---
""")
gr.Markdown("""
> **Important**: This AI tool is for **emotional support and education only**.
> It is NOT a replacement for professional therapy or psychiatric care.
> **If you are in crisis, call 988 (US), 116 123 (UK), or your local emergency number.**
""")
with gr.Tabs():
# === TAB 1: Therapeutic Chat ===
with gr.Tab("\U0001f9e0 Chat"):
chatbot = gr.Chatbot(
label="Therapeutic Conversation",
height=500,
show_label=True,
type="tuples",
)
with gr.Row():
msg = gr.Textbox(
label="Share how you're feeling",
placeholder="Tell me what's on your mind... (or try: 'PHQ-9', 'GAD-7', 'journal [entry]', 'CBT', 'DBT', 'mindfulness')",
lines=3,
scale=5,
)
send_btn = gr.Button("Send", variant="primary", scale=1)
with gr.Row():
clear_btn = gr.Button("New Session", variant="secondary")
# === TAB 2: Voice Analysis ===
with gr.Tab("\U0001f3a4 Voice Analysis"):
gr.Markdown("""
### Voice Stress Analysis
Your voice carries **emotional biomarkers** invisible to text alone.
Record or upload a voice sample to add vocal data to your session.
**What we detect**: Pitch patterns (anxiety), speech rate (stress), energy levels (agitation),
voice tremor/jitter (instability), shimmer (emotional distress)
""")
audio_input = gr.Audio(
sources=["microphone", "upload"],
type="filepath",
label="Record or Upload (max 60 seconds)",
)
voice_btn = gr.Button("Analyze Voice", variant="primary")
voice_result = gr.Markdown(label="Voice Analysis Results", value="*Results will appear here after analysis.*")
# === TAB 3: Facial Expression Analysis ===
with gr.Tab("\U0001f4f8 Facial Analysis"):
gr.Markdown("""
### Facial Expression Analysis
Capture or upload a photo for **AI-powered emotion detection**.
Results combine with text and voice for a **multimodal emotional assessment**.
**Emotions detected**: Anger, Fear, Happiness, Sadness, Surprise, Neutral, Disgust, Contempt
""")
image_input = gr.Image(
sources=["webcam", "upload"],
type="filepath",
label="Capture or Upload Photo",
)
face_btn = gr.Button("Analyze Expression", variant="primary")
face_result = gr.Markdown(label="Facial Analysis Results", value="*Results will appear here after analysis.*")
# Event handlers
msg.submit(respond, [msg, chatbot], [msg, chatbot])
send_btn.click(respond, [msg, chatbot], [msg, chatbot])
voice_btn.click(analyze_voice, [audio_input, chatbot], [chatbot, voice_result])
face_btn.click(analyze_face, [image_input, chatbot], [chatbot, face_result])
clear_btn.click(clear_session, outputs=[chatbot, msg, audio_input, image_input])
gr.Markdown("""
---
### Available Commands
| Command | What It Does |
|---------|-------------|
| `PHQ-9` | Depression screening (validated clinical tool) |
| `GAD-7` | Anxiety screening (validated clinical tool) |
| `journal [text]` | Save a journal entry with AI reflection |
| `CBT` | Switch to Cognitive Behavioral Therapy mode |
| `DBT` | Switch to Dialectical Behavior Therapy mode |
| `ACT` | Switch to Acceptance & Commitment Therapy mode |
| `mindfulness` | Switch to Mindfulness exercises |
### What Makes This 10x Better
| Feature | Wysa/Woebot | MindCare AI |
|---------|------------|-------------|
| Emotion Detection | Keyword only | Multi-dimensional + multimodal |
| Therapy Modes | 1-2 | 6 full modalities |
| Crisis Response | Basic | Full pipeline with resources |
| Voice Analysis | None | Vocal stress biomarkers |
| Facial Analysis | None | AI emotion from expressions |
| Screening Tools | None | PHQ-9 + GAD-7 validated |
| Mood Tracking | Basic | Visual trends with analysis |
| Session Memory | None | Full session continuity |
""")
if __name__ == "__main__":
demo.launch(show_error=True)