File size: 4,697 Bytes
8aa3867
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# ============================================================
# FILE: app/api.py
# PURPOSE: Acts as the central "loader" for all heavy AI objects.
#          Streamlit reruns the entire script on every user action,
#          so we use @st.cache_resource to load models ONCE and
#          reuse the same object for the entire app session.
# ============================================================

import os       # Standard library: used to build file paths safely
import sys      # Standard library: used to modify Python's module search path
import streamlit as st  # Streamlit: the web framework powering the entire UI

# ─────────────────────────────────────────────────────────────
# PATH SETUP BLOCK
# Problem: This file lives inside app/ but our source code lives
#          in src/ (one level up). Python won't find src/ unless
#          we manually tell it where to look.
# ─────────────────────────────────────────────────────────────

# Get the absolute path of THIS file (e.g., /home/user/project/app/api.py)
current_dir = os.path.dirname(os.path.abspath(__file__))

# Go one level UP from app/ to reach the project root (e.g., /home/user/project/)
project_root = os.path.abspath(os.path.join(current_dir, "../"))

# Only add the project root to sys.path if it isn't already there.
# sys.path is the list of directories Python searches when you do "import X".
if project_root not in sys.path:
    sys.path.append(project_root)

# ─────────────────────────────────────────────────────────────
# IMPORTS β€” now safe because project_root is on sys.path
# ─────────────────────────────────────────────────────────────

# Import our main chatbot class from src/chatbot/groq_bot.py
from src.chatbot.groq_bot import MindGuardChatbot

# Import our SHAP explainability class from src/explainability/shap_explainer.py
from src.explainability.shap_explainer import MindGuardSHAPExplainer


# ─────────────────────────────────────────────────────────────
# CACHED LOADER: MindGuard Chatbot
# ─────────────────────────────────────────────────────────────

@st.cache_resource   # <-- This decorator is the KEY. It tells Streamlit:
                     # "Run this function only ONCE. After that, return the
                     #  same object every time instead of rebuilding it."
                     # Without this, the bot would reload on every keypress.
def get_mindguard_bot():
    """
    Instantiates the MindGuardChatbot and keeps it alive in memory.
    This loads:
      - The Groq LLM connection
      - The Whisper audio transcription model
      - The SQLite database connection
    All of these are expensive to create, so we create them once.
    """
    return MindGuardChatbot()   # Create and return a new bot instance


# ─────────────────────────────────────────────────────────────
# CACHED LOADER: SHAP Explainer
# ─────────────────────────────────────────────────────────────

@st.cache_resource   # Same caching strategy as above β€” critical here because
                     # MindGuardSHAPExplainer loads a full XLM-RoBERTa model
                     # (~1.1GB weights) which takes ~10-15 seconds to load.
                     # Caching means that cost is paid only once at startup.
def get_shap_explainer():
    """
    Instantiates the MindGuardSHAPExplainer and keeps it alive in memory.
    This loads:
      - XLMRobertaTokenizer (converts text to token IDs)
      - XLMRobertaForSequenceClassification (the 35-emotion neural network)
      - shap.Explainer (the Game Theory math engine wrapped around the model)
    All three are heavy objects β€” caching is non-negotiable for a smooth UX.
    """
    return MindGuardSHAPExplainer()  # Create and return a new explainer instance