Enhanced-DilBot / app.py
zainali9091's picture
Create app.py
f195ec2 verified
import streamlit as st
import os, json, datetime, hashlib
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_groq import ChatGroq
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from gtts import gTTS
from pathlib import Path
from dotenv import load_dotenv
from sentence_transformers import SentenceTransformer, util
import altair as alt
import speech_recognition as sr
from transformers import pipeline
import torch
import pickle
import re
import matplotlib.pyplot as plt # Import matplotlib for the new chart
# Load environment variables
load_dotenv()
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
CRISIS_KEYWORDS = ["suicide", "kill myself", "end it all", "worthless", "can't go on", "hurt myself", "self harm", "want to disappear", "no reason to live"]
# Initialize session state
if "authenticated" not in st.session_state:
st.session_state.authenticated = False
if "username" not in st.session_state:
st.session_state.username = None
if "is_admin" not in st.session_state:
st.session_state.is_admin = False
if "transcribed_text" not in st.session_state:
st.session_state.transcribed_text = ""
if "page" not in st.session_state: # New: Manage current page view
st.session_state.page = "auth" # 'auth', 'main_app', 'admin_dashboard', 'about_us'
# Admin configuration
ADMIN_USERNAME = os.getenv("ADMIN_USERNAME") # Set in HF Spaces secrets
ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") # Set in HF Spaces secrets
# User management functions
def hash_password(password):
"""Hash password using SHA-256 with salt"""
salt = "dilbot_secure_salt_2024" # You can change this
return hashlib.sha256((password + salt).encode()).hexdigest()
def get_secure_users_path():
"""Get path to users file in a hidden directory"""
secure_dir = ".secure_data"
os.makedirs(secure_dir, exist_ok=True)
return os.path.join(secure_dir, "users_encrypted.json")
def load_users():
"""Load users from secure file"""
users_path = get_secure_users_path()
if os.path.exists(users_path):
try:
with open(users_path, "r") as f:
return json.load(f)
except:
return {}
return {}
def save_users(users):
"""Save users to secure file"""
users_path = get_secure_users_path()
with open(users_path, "w") as f:
json.dump(users, f, indent=4)
def create_user_directory(username):
"""Create user-specific directory structure"""
user_dir = f"users/{username}"
os.makedirs(user_dir, exist_ok=True)
return user_dir
def get_user_file_path(username, filename):
"""Get path to user-specific file"""
user_dir = f"users/{username}"
return os.path.join(user_dir, filename)
def signup(username, password, email):
"""Register new user"""
users = load_users()
if username in users:
return False, "Username already exists"
email_pattern = r"^[\w\.-]+@[\w\.-]+\.\w+$"
if not re.match(email_pattern, email):
return False, "Invalid email format"
users[username] = {
"password": hash_password(password),
"email": email,
"created_at": str(datetime.datetime.now())
}
save_users(users)
create_user_directory(username)
return True, "Account created successfully!"
def login(username, password):
"""Authenticate user or admin"""
# Check if admin login
if username == ADMIN_USERNAME and password == ADMIN_PASSWORD:
return True, "Admin login successful!", True
# Regular user login
users = load_users()
if username not in users:
return False, "User not found.Please signup.", False
if users[username]["password"] == hash_password(password):
return True, "Login successful!", False
return False, "Incorrect password", False
# Emotion detection
@st.cache_resource
def load_emotion_model():
return pipeline(
"text-classification",
model="j-hartmann/emotion-english-distilroberta-base",
top_k=1,
device=-1
)
def detect_emotion(text):
emotion_pipeline = load_emotion_model()
prediction = emotion_pipeline(text)[0][0]
return prediction['label'].lower(), prediction['score']
# === UI/UX Modifications (Applied globally) ===
def set_background_and_styles():
st.markdown(
"""
<style>
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&display=swap');
.stApp {
background: linear-gradient(135deg, #e0e7ff 0%, #c6e2ff 50%, #b0c4de 100%); /* Light blueish gradient */
background-size: cover;
background-position: center;
background-repeat: no-repeat;
background-attachment: fixed;
font-family: 'Montserrat', sans-serif;
color: #333; /* Default text color to dark for light background */
}
h1, h2, h3, h4, h5, h6, .stMarkdown, label {
font-family: 'Merriweather', serif;
color: #1a237e; /* Darker blue for headings */
}
/* Ensure text area label is dark */
.stTextArea > label {
color: #333 !important;
}
.stButton>button {
background-image: linear-gradient(to right, #6a11cb 0%, #2575fc 100%); /* Blue-purple gradient */
color: white;
border-radius: 8px;
border: none;
padding: 10px 20px;
font-size: 16px;
font-weight: bold;
transition: all 0.2s ease-in-out;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.stButton>button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
}
/* Secondary button style for "Explore Opportunities" */
.secondary-button > button {
background-color: white;
color: #6a11cb;
border: 1px solid #6a11cb;
box-shadow: none;
}
.secondary-button > button:hover {
background-color: #f0f4f8; /* Light hover */
color: #2575fc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.stTextInput>div>div>input, .stTextArea>div>div>textarea, .stSelectbox>div>div>div {
border-radius: 8px;
border: 1px solid #b3e5fc; /* Light blue border */
padding: 10px;
background-color: rgba(255, 255, 255, 0.95); /* Nearly opaque white */
color: #333; /* Input text color dark */
}
/* Adjusted stSuccess and stInfo for white text on darker transparent black background */
.stSuccess {
border-left: 5px solid #28a745;
background-color: rgba(0, 0, 0, 0.6); /* Transparent black */
color: white; /* Make text white */
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
}
.stInfo {
border-left: 5px solid #17a2b8;
background-color: rgba(0, 0, 0, 0.6); /* Transparent black */
color: white; /* Make text white */
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
}
/* Keeping warning/error as original light background for now, as black transparent might not suit warnings well */
.stWarning {
border-left: 5px solid #ffc107;
background-color: rgba(255, 255, 255, 0.9);
color: #333;
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
}
.stError {
border-left: 5px solid #dc3545;
background-color: rgba(255, 255, 255, 0.9);
color: #333;
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;
}
/* Custom container for content with blur background - used for auth form */
.auth-content-container {
background-color: rgba(255, 255, 255, 0.8); /* Slightly transparent white */
backdrop-filter: blur(8px); /* Blur effect */
border-radius: 15px;
padding: 30px;
margin: 20px auto;
max-width: 450px; /* Slimmer for auth forms */
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
text-align: center; /* Center content within */
}
/* Main app content container */
.main-app-content-container {
background-color: rgba(255, 255, 255, 0.8); /* Slightly transparent white */
backdrop-filter: blur(8px); /* Blur effect */
border-radius: 15px;
padding: 30px;
margin: 20px auto;
max-width: 800px; /* Wider for main app content */
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
color: #333; /* Dark text inside */
}
/* Ensure headers within main app container are dark */
.main-app-content-container h1, .main-app-content-container h2,
.main-app-content-container h3, .main-app-content-container h4,
.main-app-content-container h5, .main-app-content-container h6,
.main-app-content-container .stMarkdown, .main-app-content-container label {
color: #1a237e; /* Darker blue for headers in main app */
}
/* Specifically target text within main-app-content-container to be dark */
.main-app-content-container p, .main-app-content-container li, .main-app-content-container div {
color: #333;
}
/* Override specific Streamlit elements that don't pick up general styles for main app */
.main-app-content-container .st-emotion-cache-1jmve6n, /* st.subheader */
.main-app-content-container .st-emotion-cache-1gcs47q, /* st.text or similar */
.main-app-content-container .st-emotion-cache-10q7f27, /* st.info text */
.main-app-content-container .st-emotion-cache-1j0qsvo { /* more specific text */
color: #333 !important;
}
/* Navbar Styling */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 40px;
background-color: rgba(255, 255, 255, 0.8); /* Slightly transparent white */
backdrop-filter: blur(5px);
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
border-radius: 10px;
margin-bottom: 20px;
}
.navbar-logo {
font-family: 'Merriweather', serif;
font-size: 24px;
font-weight: bold;
color: #1a237e;
}
.navbar-links {
display: flex;
gap: 30px;
}
.navbar-link {
font-family: 'Montserrat', sans-serif;
font-size: 16px;
color: #3f51b5; /* Medium blue */
text-decoration: none;
padding: 5px 10px;
transition: color 0.2s ease-in-out;
cursor: pointer;
}
.navbar-link:hover {
color: #6a11cb; /* Purple hover */
}
.navbar-button {
background-image: linear-gradient(to right, #6a11cb 0%, #2575fc 100%);
color: white;
border-radius: 8px;
padding: 8px 15px;
font-size: 14px;
font-weight: bold;
text-decoration: none;
transition: all 0.2s ease-in-out;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer;
}
.navbar-button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
/* AI Powered Educational Platform banner style */
.ai-powered-banner {
display: inline-flex; /* Use inline-flex to shrink-wrap content */
align-items: center;
background-color: rgba(255, 255, 255, 0.7); /* Light, semi-transparent */
border-radius: 20px;
padding: 8px 15px;
margin-bottom: 20px;
font-size: 14px;
font-weight: 600;
color: #3f51b5; /* Blue text */
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
text-align: center; /* Ensure text is centered if container grows */
margin-left: auto; /* Center horizontally */
margin-right: auto; /* Center horizontally */
}
.ai-powered-banner span {
margin-right: 8px;
font-size: 16px; /* Icon size */
}
/* Centering the banner */
.centered-container {
display: flex;
justify-content: center;
width: 100%;
}
/* Metrics section styles (50K+ Students Helped, etc.) */
.metrics-container {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
margin-top: 40px;
gap: 20px;
}
.metric-item {
text-align: center;
background-color: rgba(255, 255, 255, 0.7);
padding: 20px;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0,0,0,0.05);
flex: 1; /* Allow items to grow/shrink */
min-width: 180px; /* Minimum width before wrapping */
}
.metric-item h2 {
font-size: 32px;
color: #6a11cb; /* Purple */
margin-bottom: 5px;
}
.metric-item p {
font-size: 16px;
color: #555;
}
.metric-item img { /* Style for icon images if used */
height: 40px;
margin-bottom: 10px;
}
/* Center plots */
.st-emotion-cache-1pxazr6 { /* Specific Streamlit container for pyplot */
display: flex;
justify-content: center;
}
/* Override specific Streamlit elements that don't pick up general styles */
.st-emotion-cache-1jmve6n { /* This class is often for st.subheader */
color: #1a237e !important; /* Darker blue */
}
.st-emotion-cache-1gcs47q { /* This class can be for specific text elements */
color: #333 !important;
}
.st-emotion-cache-10q7f27 { /* Example for st.info text */
color: #333 !important;
}
/* Hide the top grey bar */
header.st-emotion-cache-1gh8zsi {
display: none !important;
}
div.st-emotion-cache-fis6y8 {
padding-top: 0 !important;
}
div.st-emotion-cache-z5inrg {
display: none !important;
}
/* Adjust overall container width */
.st-emotion-cache-fg4lbf {
max-width: 1000px !important; /* Adjusted for wider layout */
padding-left: 0 !important;
padding-right: 0 !important;
}
.block-container {
padding-left: 1rem;
padding-right: 1rem;
color: #333; /* Default text color within block-container */
}
</style>
""",
unsafe_allow_html=True
)
# === Load Quotes ===
QUOTES_PATH = os.path.join(os.path.dirname(__file__), "quotes.json")
# Create a dummy quotes.json if it doesn't exist for the script to run locally without error
if not os.path.exists(QUOTES_PATH):
dummy_quotes = {
"positive": ["You are doing great!", "Keep shining!", "Believe in yourself!"],
"negative": ["It's okay to feel down, brighter days are ahead.", "Take a deep breath.", "This too shall pass."],
"neutral": ["Observe your thoughts.", "Find your center.", "Acknowledge your feelings."]
}
with open(QUOTES_PATH, "w") as f:
json.dump(dummy_quotes, f, indent=4)
with open(QUOTES_PATH, "r") as f:
quotes_data = json.load(f)
# === Emotion detection
@st.cache_resource
def load_emotion_model():
return pipeline(
"text-classification",
model="j-hartmann/emotion-english-distilroberta-base",
top_k=1,
device=-1
)
def detect_emotion(text):
emotion_pipeline = load_emotion_model()
prediction = emotion_pipeline(text)[0][0]
return prediction['label'].lower(), prediction['score']
def is_crisis(text):
"""Check for crisis keywords"""
return any(phrase in text.lower() for phrase in CRISIS_KEYWORDS)
def build_user_vectorstore(username, quotes):
"""Build and save user-specific vectorstore"""
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
vectorstore = FAISS.from_texts(quotes, embedding=embeddings)
# Save vectorstore for user
vectorstore_path = get_user_file_path(username, "vectorstore")
vectorstore.save_local(vectorstore_path)
return vectorstore
def load_user_vectorstore(username):
"""Load user-specific vectorstore"""
vectorstore_path = get_user_file_path(username, "vectorstore")
if os.path.exists(vectorstore_path):
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
return FAISS.load_local(vectorstore_path, embeddings, allow_dangerous_deserialization=True)
return None
def save_user_journal(username, user_input, emotion, score, response):
"""Save journal entry for specific user"""
journal_path = get_user_file_path(username, "journal.json")
entry = {
"date": str(datetime.date.today()),
"timestamp": str(datetime.datetime.now()),
"user_input": user_input,
"emotion": emotion,
"confidence": round(score * 100, 2),
"response": response
}
journal = []
if os.path.exists(journal_path):
with open(journal_path, "r") as f:
journal = json.load(f)
journal.append(entry)
with open(journal_path, "w") as f:
json.dump(journal, f, indent=4)
def load_user_journal(username):
"""Load journal for specific user"""
journal_path = get_user_file_path(username, "journal.json")
if os.path.exists(journal_path):
with open(journal_path, "r") as f:
return json.load(f)
return []
def speak(text, username):
"""Generate and play audio response"""
tts = gTTS(text=text, lang='en')
audio_path = get_user_file_path(username, "response.mp3")
tts.save(audio_path)
st.audio(audio_path, format="audio/mp3")
def transcribe_audio_file(uploaded_audio):
"""Transcribe uploaded audio file"""
recognizer = sr.Recognizer()
try:
with sr.AudioFile(uploaded_audio) as source:
audio_data = recognizer.record(source)
text = recognizer.recognize_google(audio_data)
return text
except Exception as e:
return f"Error: {str(e)}"
# === Navbar Component ===
def render_navbar():
st.markdown(
"""
<div class="navbar">
<div class="navbar-logo">DilBot</div>
<div class="navbar-links">
<a href="#" onclick="streamlit.setSessionState({page: 'main_app'});" class="navbar-link">Chatbot</a>
<a href="#" onclick="streamlit.setSessionState({page: 'about_us'});" class="navbar-link">About Us</a>
<a href="#" onclick="streamlit.setSessionState({page: 'journal'});" class="navbar-link">Journal</a>
{}
</div>
<a href="#" onclick="streamlit.setSessionState({authenticated: false, username: null, is_admin: false, page: 'auth'});" class="navbar-button">Logout</a>
</div>
""".format(
'<a href="#" onclick="streamlit.setSessionState({page: \'admin_dashboard\'});" class="navbar-link">Admin Dashboard</a>' if st.session_state.is_admin else ''
),
unsafe_allow_html=True
)
# === Pages ===
def show_auth_page():
st.set_page_config(page_title="DilBot - Login", page_icon="๐Ÿง ", layout="centered")
set_background_and_styles() # Apply global styles
st.markdown(
"""
<div class="navbar">
<div class="navbar-logo">DilBot</div>
<div class="navbar-links">
</div>
</div>
""", unsafe_allow_html=True
)
st.markdown("<div class='centered-container'><div class='ai-powered-banner'><span>โœจ</span>AI-Powered Emotional Companion</div></div>", unsafe_allow_html=True)
st.markdown(
"""
<h1 style='text-align: center; font-size: 3.5em; color: #1a237e;'>Transform Your Emotional Journey</h1>
<p style='text-align: center; font-size: 1.2em; color: #555;'>
DilBot empowers individuals with AI-driven insights for emotional well-being, personal growth, and self-discovery worldwide.
</p>
""", unsafe_allow_html=True
)
col_btn1, col_btn2 = st.columns([0.6, 0.4])
with col_btn1:
if st.button("Get Started Free โ†’", key="get_started_btn"):
st.session_state.page = "main_app" # Automatically go to main app (or login/signup if not authenticated)
# This button will effectively trigger the login/signup tabs below
pass # The logic for login/signup is below in the tabs
with col_btn2:
st.markdown('<div class="secondary-button">', unsafe_allow_html=True)
if st.button("Explore Opportunities", key="explore_btn"):
st.session_state.page = "about_us"
st.rerun()
st.markdown('</div>', unsafe_allow_html=True)
st.markdown("<div class='auth-content-container'>", unsafe_allow_html=True) # Container for login/signup
tab1, tab2 = st.tabs(["Login", "Sign Up"])
with tab1:
st.subheader("Login to Your Account")
login_username = st.text_input("Username", key="login_user")
login_password = st.text_input("Password", type="password", key="login_pass")
if st.button("Login", key="login_btn"):
if login_username and login_password:
success, message, is_admin = login(login_username, login_password)
if success:
st.session_state.authenticated = True
st.session_state.username = login_username
st.session_state.is_admin = is_admin
st.success(message)
st.session_state.page = "main_app" if not is_admin else "admin_dashboard" # Direct to correct page
st.rerun()
else:
st.error(message)
else:
st.warning("Please fill in all fields")
with tab2:
st.subheader("Create New Account")
signup_username = st.text_input("Choose Username", key="signup_user")
signup_email = st.text_input("Email Address", key="signup_email")
signup_password = st.text_input("Choose Password", type="password", key="signup_pass")
signup_confirm = st.text_input("Confirm Password", type="password", key="signup_confirm")
if st.button("Create Account", key="signup_btn"):
if all([signup_username, signup_email, signup_password, signup_confirm]):
if signup_password != signup_confirm:
st.error("Passwords don't match!")
elif len(signup_password) < 6:
st.error("Password must be at least 6 characters long!")
else:
success, message = signup(signup_username, signup_password, signup_email)
if success:
st.success(message)
st.info("You can now login with your credentials!")
else:
st.error(message)
else:
st.warning("Please fill in all fields")
st.markdown("</div>", unsafe_allow_html=True) # Close auth-content-container
def show_about_us_page():
st.set_page_config(page_title="DilBot - About Us", page_icon="๐Ÿ’ก", layout="wide")
set_background_and_styles()
render_navbar()
st.markdown("<div class='main-app-content-container'>", unsafe_allow_html=True)
st.title("๐Ÿ’ก About DilBot")
st.markdown("---")
st.header("Description")
st.write("""
DilBot is an AI-powered emotional companion designed to help individuals navigate their feelings, practice self-reflection, and foster emotional well-being.
Through empathetic conversations and personalized insights, DilBot aims to provide a supportive space for users to explore their emotional landscape.
It uses advanced natural language processing to understand user sentiments and offers relevant guidance and motivational quotes.
""")
st.header("About Us")
st.write("""
We are a passionate team of six, dedicated to leveraging technology for mental wellness. This project was developed as part of a CSG Hackathon, driven by a shared vision to create accessible emotional support.
""")
st.subheader("The Team Who Built This:")
st.markdown("""
* **Syed Zain:** Spearheaded front-end polish and contributed to backend development.
* **Ahmad:** Focused on robust backend implementation.
* **Zunaira:** Contributed significantly to backend functionalities.
* **Awais:** Instrumental in filtering different errors and ensuring system stability.
* **Ume-habiba:** Diligently documented this entire project.
""")
st.header("Information")
st.write("""
DilBot operates by analyzing the sentiment of your input and offering a supportive response tailored to your emotional state.
It integrates a personalized journal to track your emotional journey over time, providing valuable insights into your mood patterns.
All your data is stored securely and privately, ensuring your conversations remain confidential.
""")
st.markdown("</div>", unsafe_allow_html=True)
def show_main_app():
"""Main DilBot application"""
username = st.session_state.username
st.set_page_config(page_title="DilBot - Emotional AI", page_icon="๐Ÿง ", layout="wide")
set_background_and_styles() # Apply global styles
render_navbar() # Render the navigation bar
st.markdown("<div class='main-app-content-container'>", unsafe_allow_html=True) # Main app container
st.title(f"๐Ÿง  DilBot - Welcome back, {username}!")
st.markdown("Your personal emotional AI companion")
st.markdown("---")
# Quote categories
quote_categories = {
"Grief": ["Grief is the price we pay for love.", "Tears are the silent language of grief.", "What we have once enjoyed we can never lose; all that we love deeply becomes a part of us."],
"Motivation": ["Believe in yourself and all that you are.", "Tough times never last, but tough people do.", "The only way to do great work is to love what you do."],
"Healing": ["Every wound has its own time to heal.", "It's okay to take your time to feel better.", "Healing is not linear, and that's perfectly okay."],
"Relationships": ["The best relationships are built on trust.", "Love is not about possession but appreciation.", "Healthy relationships require both people to show up authentically."]
}
# UI for quote selection and file upload
col1, col2 = st.columns(2)
with col1:
selected_category = st.selectbox("๐ŸŽฏ Choose a quote theme:", list(quote_categories.keys()))
with col2:
uploaded_quotes = st.file_uploader("๐Ÿ“ Upload your own quotes (.txt)", type=["txt"])
uploaded_audio = st.file_uploader("๐ŸŽค Upload a voice message (.wav)", type=["wav"])
# Handle vectorstore
current_quotes = []
vectorstore = None
if uploaded_quotes:
custom_quotes = uploaded_quotes.read().decode("utf-8").splitlines()
custom_quotes = [quote.strip() for quote in custom_quotes if quote.strip()]
vectorstore = build_user_vectorstore(username, custom_quotes)
current_quotes = custom_quotes
st.success(f"โœ… {len(custom_quotes)} custom quotes uploaded and saved!")
else:
default_quotes = quote_categories[selected_category]
vectorstore = load_user_vectorstore(username)
if vectorstore is None:
vectorstore = build_user_vectorstore(username, default_quotes)
current_quotes = default_quotes
# Voice transcription
if uploaded_audio and st.button("๐ŸŽ™๏ธ Transcribe Voice"):
with st.spinner("Transcribing your voice..."):
transcribed = transcribe_audio_file(uploaded_audio)
if transcribed.startswith("Error:"):
st.error(transcribed)
else:
st.session_state.transcribed_text = transcribed
st.success("โœ… Voice transcribed successfully!")
# Input area
user_input = st.text_area(
"๐Ÿ’ฌ What's on your mind?",
value=st.session_state.transcribed_text,
height=100,
placeholder="Share your thoughts, feelings, or experiences..."
)
final_input = user_input.strip() or st.session_state.transcribed_text.strip()
# Main interaction button
if st.button("๐Ÿง  Talk to DilBot", type="primary"):
if not final_input:
st.warning("โš ๏ธ Please enter something to share or upload a voice message.")
else:
with st.spinner("DilBot is thinking and feeling..."):
# Emotion detection
emotion, score = detect_emotion(final_input)
# Get AI response
prompt_template = PromptTemplate(
input_variables=["context", "user_input", "username"],
template="""You are DilBot, an empathetic emotional support AI companion for {username}.
Use the following emotional quote context to respond gently, supportively, and personally.
Context quotes:
{context}
User's message:
{user_input}
Respond as DilBot with warmth, empathy, and understanding. Keep it conversational and supportive."""
)
# Get similar quotes
similar_docs = vectorstore.similarity_search(final_input, k=2)
context = "\n".join([doc.page_content for doc in similar_docs])
# Generate response
groq_llm = ChatGroq(api_key=GROQ_API_KEY, model="llama3-70b-8192")
chain = LLMChain(llm=groq_llm, prompt=prompt_template)
response = chain.run(context=context, user_input=final_input, username=username)
# Save to user's journal
save_user_journal(username, final_input, emotion, score, response)
# Display results
col1, col2 = st.columns([2, 1])
with col1:
st.success(f"**Emotion Detected:** {emotion.capitalize()} ({round(score*100)}% confidence)")
with col2:
if is_crisis(final_input):
st.error("๐Ÿšจ Crisis detected! Please reach out to a mental health professional immediately.")
# Show relevant quote
if current_quotes:
model = SentenceTransformer("all-MiniLM-L6-v2")
quote_embeddings = model.encode(current_quotes, convert_to_tensor=True)
user_embedding = model.encode(final_input, convert_to_tensor=True)
sims = util.pytorch_cos_sim(user_embedding, quote_embeddings)[0]
best_match = sims.argmax().item()
selected_quote = current_quotes[best_match]
st.info(f"๐Ÿ’ญ **Quote for you:** *{selected_quote}*")
# Show response
st.markdown("### ๐Ÿค– DilBot's Response:")
st.markdown(f"> {response}")
# Audio response
speak(response, username)
# Clear transcribed text after successful interaction
st.session_state.transcribed_text = ""
# === New Chart Graph (Overall App Metrics - Placeholder Data) ===
st.markdown("---")
st.header("๐Ÿ“Š Overall App Engagement")
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5), dpi=100) # Increased figsize for better visibility
# Data mirroring Chart Graph.png proportions
bar_labels = ['A', 'B', 'C', 'D', 'E']
bar_values = [100, 80, 40, 65, 30] # Approximate percentages from image
doughnut_values = [35, 30, 15, 15, 5] # Approximate percentages from image
# Colors for charts (matching example or chosen for aesthetic)
# Using dark blue, light blue, grey, orange from the image
chart_colors = ['#1f4e79', '#4a90e2', '#d3d3d3', '#f5a623', '#9b9b9b']
# Set chart backgrounds to white for contrast as per previous request
ax1.set_facecolor('white')
ax2.set_facecolor('white')
fig.patch.set_facecolor('white') # Entire figure background white
# Bar Chart (Left)
ax1.bar(bar_labels, bar_values, color=chart_colors)
ax1.set_title("Engagement Categories", color='black') # Title color
ax1.set_ylabel("Percentage", color='black') # Label color
ax1.tick_params(axis='x', colors='black') # X-axis tick labels color
ax1.tick_params(axis='y', colors='black') # Y-axis tick labels color
ax1.set_ylim(0, 110) # Set Y-limit for better scaling
ax1.spines['bottom'].set_color('black') # Axis line colors
ax1.spines['left'].set_color('black')
ax1.spines['top'].set_visible(False) # Hide top and right spines
ax1.spines['right'].set_visible(False)
# Doughnut Chart (Right)
if sum(doughnut_values) > 0:
wedges, texts, autotexts = ax2.pie(
doughnut_values, labels=bar_labels, autopct='%1.1f%%',
colors=chart_colors, startangle=90, wedgeprops=dict(width=0.4), pctdistance=0.85
)
ax2.set_title("Usage Distribution", color='black') # Title color
for text in texts:
text.set_color('black') # Label color
for autotext in autotexts:
autotext.set_color('black') # Percentage text color
else:
ax2.text(0.5, 0.5, 'No data', horizontalalignment='center', verticalalignment='center', transform=ax2.transAxes, color='black')
# Ensure equal aspect ratio for pie chart to be circular
ax2.axis('equal')
st.pyplot(fig)
plt.close(fig)
# User's personal dashboard (existing content, now within main-app-content-container)
st.markdown("---")
st.header("๐Ÿ“Š Your Personal Dashboard")
# Load user's journal
journal_data = load_user_journal(username)
if journal_data:
# Mood tracker
st.subheader("๐Ÿ“ˆ Your Daily Mood Tracker")
# Prepare data for chart
df_data = []
for entry in journal_data:
df_data.append({
"date": entry["date"],
"emotion": entry["emotion"].capitalize(),
"confidence": entry["confidence"]
})
if df_data:
chart = alt.Chart(alt.Data(values=df_data)).mark_bar().encode(
x=alt.X('date:N', title='Date', axis=alt.Axis(labelAngle=-45)),
y=alt.Y('count():Q', title='Frequency'),
color=alt.Color('emotion:N', title='Emotion'),
tooltip=['date:N', 'emotion:N', 'count():Q']
).properties(
width=600,
height=300,
title="Your Emotional Journey Over Time"
).interactive() # Make chart interactive for zooming/panning
st.altair_chart(chart, use_container_width=True)
# Recent conversations
st.subheader("๐Ÿ’ฌ Recent Conversations")
recent_entries = journal_data[-5:] if len(journal_data) >= 5 else journal_data
for i, entry in enumerate(reversed(recent_entries)):
with st.expander(f"๐Ÿ“… {entry['date']} - {entry['emotion'].capitalize()} ({entry['confidence']}%)"):
st.markdown(f"**You said:** {entry['user_input']}")
st.markdown(f"**DilBot replied:** {entry['response']}")
# Statistics
st.subheader("๐Ÿ“Š Your Emotional Statistics")
col1, col2, col3 = st.columns(3)
with col1:
st.metric("Total Conversations", len(journal_data))
with col2:
emotions = [entry['emotion'] for entry in journal_data]
most_common = max(set(emotions), key=emotions.count) if emotions else "None"
st.metric("Most Common Emotion", most_common.capitalize())
with col3:
if journal_data:
avg_confidence = sum(entry['confidence'] for entry in journal_data) / len(journal_data)
st.metric("Avg. Confidence", f"{avg_confidence:.1f}%")
else:
st.info("๐ŸŒŸ Start your first conversation with DilBot to see your personal dashboard!")
st.markdown("---")
st.caption("Built by Members of CSG Hackathon Team | Your data is stored privately and securely")
st.markdown("</div>", unsafe_allow_html=True) # Close main-app-content-container
def get_admin_stats():
"""Get comprehensive admin statistics"""
users = load_users()
stats = {
"total_users": len(users),
"users_today": 0,
"users_this_week": 0,
"total_conversations": 0,
"active_users": 0,
"user_details": []
}
today = datetime.date.today()
week_ago = today - datetime.timedelta(days=7)
for username, user_data in users.items():
created_date = datetime.datetime.fromisoformat(user_data["created_at"]).date()
# Count registrations
if created_date == today:
stats["users_today"] += 1
if created_date >= week_ago:
stats["users_this_week"] += 1
# Get user journal stats
journal_data = load_user_journal(username)
conversation_count = len(journal_data)
stats["total_conversations"] += conversation_count
if conversation_count > 0:
stats["active_users"] += 1
last_activity = journal_data[-1]["date"] if journal_data else "Never"
else:
last_activity = "Never"
# Get emotion breakdown
emotions = [entry['emotion'] for entry in journal_data]
emotion_counts = {}
for emotion in emotions:
emotion_counts[emotion] = emotion_counts.get(emotion, 0) + 1
most_common_emotion = max(emotion_counts, key=emotion_counts.get) if emotion_counts else "None"
stats["user_details"].append({
"username": username,
"email": user_data["email"],
"joined": created_date.strftime("%Y-%m-%d"),
"conversations": conversation_count,
"last_activity": last_activity,
"most_common_emotion": most_common_emotion.capitalize(),
"emotions_breakdown": emotion_counts
})
return stats
def log_admin_activity(action, details=""):
"""Log admin activities"""
admin_log_path = "data/admin_log.json" # Persistent and visible
log_entry = {
"timestamp": str(datetime.datetime.now()),
"action": action,
"details": details,
"admin": st.session_state.username
}
admin_log = []
if os.path.exists(admin_log_path):
with open(admin_log_path, "r") as f:
admin_log = json.load(f)
admin_log.append(log_entry)
# Keep only last 100 entries
admin_log = admin_log[-100:]
os.makedirs("data", exist_ok=True)
with open(admin_log_path, "w") as f:
json.dump(admin_log, f, indent=4)
def get_admin_logs():
"""Get admin activity logs"""
admin_log_path = "data/admin_log.json"
if os.path.exists(admin_log_path):
with open(admin_log_path, "r") as f:
return json.load(f)
return []
def show_admin_dashboard():
"""Admin dashboard for monitoring users and app usage"""
st.set_page_config(page_title="DilBot Admin Dashboard", page_icon="๐Ÿ‘‘", layout="wide")
set_background_and_styles()
render_navbar()
st.markdown("<div class='main-app-content-container'>", unsafe_allow_html=True) # Admin dashboard in main container
# Header
col1, col2 = st.columns([4, 1])
with col1:
st.title("๐Ÿ‘‘ DilBot Admin Dashboard")
st.markdown("Monitor users, conversations, and app analytics")
with col2:
if st.button("Logout", key="admin_logout"):
st.session_state.authenticated = False
st.session_state.username = None
st.session_state.is_admin = False
st.session_state.page = "auth"
st.rerun()
# Log admin access
log_admin_activity("Dashboard Access", "Viewed admin dashboard")
# Get statistics
stats = get_admin_stats()
# Overview metrics
st.header("๐Ÿ“Š Overview")
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Total Users", stats["total_users"])
with col2:
st.metric("Active Users", stats["active_users"])
with col3:
st.metric("New Today", stats["users_today"])
with col4:
st.metric("Total Conversations", stats["total_conversations"])
# User registration trend
st.header("๐Ÿ“ˆ User Registration Trend")
if stats["user_details"]:
# Create registration data
reg_data = {}
for user in stats["user_details"]:
date = user["joined"]
reg_data[date] = reg_data.get(date, 0) + 1
chart_data = [{"date": date, "registrations": count} for date, count in sorted(reg_data.items())]
if chart_data:
chart = alt.Chart(alt.Data(values=chart_data)).mark_line(point=True).encode(
x=alt.X('date:T', title='Date'),
y=alt.Y('registrations:Q', title='New Registrations'),
tooltip=['date:T', 'registrations:Q']
).properties(
width=700,
height=300,
title="Daily User Registrations"
)
st.altair_chart(chart, use_container_width=True)
# Detailed user table
st.header("๐Ÿ‘ฅ User Details")
# Search and filter
col1, col2 = st.columns([2, 1])
with col1:
search_term = st.text_input("๐Ÿ” Search users", placeholder="Search by username or email")
with col2:
min_conversations = st.number_input("Min conversations", min_value=0, value=0)
# Filter users
filtered_users = stats["user_details"]
if search_term:
filtered_users = [u for u in filtered_users if
search_term.lower() in u["username"].lower() or
search_term.lower() in u["email"].lower()]
if min_conversations > 0:
filtered_users = [u for u in filtered_users if u["conversations"] >= min_conversations]
# Display user table
if filtered_users:
for user in filtered_users:
with st.expander(f"๐Ÿ‘ค {user['username']} ({user['conversations']} conversations)"):
col1, col2 = st.columns(2)
with col1:
st.write(f"**Email:** {user['email']}")
st.write(f"**Joined:** {user['joined']}")
st.write(f"**Last Activity:** {user['last_activity']}")
with col2:
st.write(f"**Conversations:** {user['conversations']}")
st.write(f"**Most Common Emotion:** {user['most_common_emotion']}")
# Show emotion breakdown
if user['emotions_breakdown']:
st.write("**Emotion Breakdown:**")
for emotion, count in user['emotions_breakdown'].items():
st.write(f" - {emotion.capitalize()}: {count}")
# Quick actions
col1, col2, col3 = st.columns(3)
with col1:
if st.button(f"View Journal", key=f"view_{user['username']}"):
# Show user's recent conversations
user_journal = load_user_journal(user['username'])
if user_journal:
st.subheader(f"Recent conversations for {user['username']}")
for entry in user_journal[-5:]:
st.text_area(
f"{entry['date']} - {entry['emotion'].capitalize()}",
f"User: {entry['user_input']}\nDilBot: {entry['response']}",
height=100,
disabled=True
)
else:
st.info("No conversations found")
with col2:
reset_key = f"reset_{user['username']}"
confirm_key = f"confirm_{user['username']}"
if st.button(f"Reset Data", key=reset_key):
st.session_state[confirm_key] = True # Flag to show confirmation
if st.session_state.get(confirm_key, False):
st.warning(f"Are you sure you want to reset data for {user['username']}?")
if st.button(f"Yes, Reset {user['username']}", key=f"confirm_reset_{user['username']}"):
# Clear user's journal
journal_path = get_user_file_path(user['username'], "journal.json")
if os.path.exists(journal_path):
os.remove(journal_path)
log_admin_activity("User Data Reset", f"Reset data for {user['username']}")
st.success(f"Data reset for {user['username']}")
st.session_state[confirm_key] = False # Reset confirmation flag
st.rerun()
if st.button(f"Cancel", key=f"cancel_reset_{user['username']}"):
st.session_state[confirm_key] = False # Cancel confirmation
st.rerun()
else:
st.info("No users found matching your criteria")
# System Analytics
st.header("๐Ÿ”ง System Analytics")
col1, col2 = st.columns(2)
with col1:
st.subheader("Emotion Distribution (All Users)")
# Aggregate all emotions
all_emotions = {}
for user in stats["user_details"]:
for emotion, count in user['emotions_breakdown'].items():
all_emotions[emotion] = all_emotions.get(emotion, 0) + count
if all_emotions:
emotion_chart_data = [{"emotion": emotion.capitalize(), "count": count}
for emotion, count in all_emotions.items()]
emotion_chart = alt.Chart(alt.Data(values=emotion_chart_data)).mark_bar().encode(
x=alt.X('emotion:N', title='Emotion'),
y=alt.Y('count:Q', title='Frequency'),
color=alt.Color('emotion:N', legend=None),
tooltip=['emotion:N', 'count:Q']
).properties(
width=400,
height=300,
title="Overall Emotion Distribution"
)
st.altair_chart(emotion_chart, use_container_width=True)
with col2:
st.subheader("User Activity Levels")
activity_levels = {"Inactive (0)": 0, "Light (1-5)": 0, "Moderate (6-20)": 0, "Heavy (21+)": 0}
for user in stats["user_details"]:
conv_count = user["conversations"]
if conv_count == 0:
activity_levels["Inactive (0)"] += 1
elif conv_count <= 5:
activity_levels["Light (1-5)"] += 1
elif conv_count <= 20:
activity_levels["Moderate (6-20)"] += 1
else:
activity_levels["Heavy (21+)"] += 1
activity_data = [{"level": level, "users": count} for level, count in activity_levels.items()]
activity_chart = alt.Chart(alt.Data(values=activity_data)).mark_arc().encode(
theta=alt.Theta('users:Q'),
color=alt.Color('level:N', title="Activity Level"),
tooltip=['level:N', 'users:Q']
).properties(
width=300,
height=300,
title="User Activity Distribution"
)
st.altair_chart(activity_chart, use_container_width=True)
# Admin logs
st.header("๐Ÿ“‹ Admin Activity Logs")
admin_logs = get_admin_logs()
if admin_logs:
# Show recent admin activities
for log_entry in reversed(admin_logs[-10:]): # Last 10 activities
timestamp = datetime.datetime.fromisoformat(log_entry["timestamp"])
st.text(f"{timestamp.strftime('%Y-%m-%d %H:%M:%S')} - {log_entry['action']}: {log_entry['details']}")
else:
st.info("No admin activities logged yet")
# Export functionality
st.header("๐Ÿ’พ Data Export")
col1, col2 = st.columns(2)
with col1:
if st.button("Export User Data (JSON)"):
export_data = {
"export_timestamp": str(datetime.datetime.now()),
"statistics": stats,
"admin_logs": admin_logs
}
st.download_button(
label="Download User Data",
data=json.dumps(export_data, indent=4),
file_name=f"dilbot_export_{datetime.date.today()}.json",
mime="application/json"
)
log_admin_activity("Data Export", "Exported user data")
with col2:
if st.button("Clear Admin Logs"):
admin_log_path = "data/admin_log.json"
if os.path.exists(admin_log_path):
os.remove(admin_log_path)
st.success("Admin logs cleared!")
st.rerun()
st.markdown("</div>", unsafe_allow_html=True) # Close main-app-content-container
# Main app logic to switch between pages
def main():
set_background_and_styles() # Apply styles globally
if not st.session_state.authenticated:
show_auth_page()
elif st.session_state.is_admin:
if st.session_state.page == "admin_dashboard":
show_admin_dashboard()
else: # If admin logs in, default to dashboard
st.session_state.page = "admin_dashboard"
st.rerun() # Rerun to display dashboard
else: # Authenticated regular user
if st.session_state.page == "main_app":
show_main_app()
elif st.session_state.page == "about_us":
show_about_us_page()
# Add other user-specific pages here if needed
else: # Default to main_app if page state is not explicitly set
st.session_state.page = "main_app"
st.rerun()
if __name__ == "__main__":
main()