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 pandas as pd
# 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 = ""
# Admin configuration
ADMIN_USERNAME = os.getenv("ADMIN_USERNAME") # Set in HF Spaces secrets
ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") # Set in HF Spaces secrets
# --- UI/UX Modifications (Applied globally) ---
def set_background_and_styles():
st.markdown(
"""
""",
unsafe_allow_html=True
)
# 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']
# Authentication UI
def show_auth_page():
set_background_and_styles()
st.markdown(
"""
DilBot
""", unsafe_allow_html=True
)
st.markdown("
✨AI-Powered Emotional Companion
", unsafe_allow_html=True)
st.markdown(
"""
Transform Your Emotional Journey
DilBot empowers individuals with AI-driven insights for emotional well-being, personal growth, and self-discovery worldwide.
""", unsafe_allow_html=True
)
st.markdown('
', unsafe_allow_html=True)
# Buttons placed directly in a single column layout
if st.button("Paid Plan", key="paid_plan_btn", use_container_width=True):
st.info("Paid version coming soon") # Text for paid plan button
st.markdown('
', unsafe_allow_html=True) # Added margin for spacing
if st.button("Login Below FOR FREE", key="login_free_btn", use_container_width=True):
st.info("Slide down below") # Text for free login button
st.markdown('
', unsafe_allow_html=True)
st.markdown('
', unsafe_allow_html=True) # Close button-row
# --- Section for icons and descriptions ---
st.markdown("
Our community experiences significant positive emotional shifts.
""", unsafe_allow_html=True)
# Second Image: Successful User's Emotion's results a happy lifestyle (https://cdn-icons-png.flaticon.com/512/10809/10809501.png)
st.markdown(f"""
Happy Lifestyle
Empowering users to achieve emotional well-being and a happier life.
""", unsafe_allow_html=True)
# Third Image: 10,000+ Daily Users (https://www.clipartmax.com/png/middle/225-2254363_checklist-comments-form-approved-icon.png)
st.markdown(f"""
10,000+ Daily Users
Join our growing community finding support and growth daily.
""", unsafe_allow_html=True)
# Re-adding the original Empathetic Chatbot feature from previous response for completeness
st.markdown("""
Empathetic Chatbot
Connect with an AI that listens and understands your feelings.
""", unsafe_allow_html=True)
st.markdown("
", unsafe_allow_html=True)
# --- End of icon and description section ---
st.markdown("
", unsafe_allow_html=True) # Container for login/signup
tab1, tab2 = st.tabs(["Login", "Sign Up"])
with tab1:
st.subheader("Login to Your Account( Admin will use own creditionals )")
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.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("
", unsafe_allow_html=True)
# Main app functions
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 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 is_crisis(text):
"""Check for crisis keywords"""
return any(phrase in text.lower() for phrase in CRISIS_KEYWORDS)
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")
# --- ENHANCED CUSTOM CSS FOR ADMIN DASHBOARD (Consistent with main app) ---
st.markdown("""
""", unsafe_allow_html=True)
# --- END CUSTOM CSS ---
# Header
col1, col2 = st.columns([4, 1])
with col1:
st.markdown(f"
", unsafe_allow_html=True)
with st.container(border=True): # Wrap metrics in a container for styling
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.markdown("
User Registration Trend
", unsafe_allow_html=True)
with st.container(border=True): # Wrap chart in a container
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
# Convert to DataFrame for Altair compatibility and sorting
chart_data_list = [{"date": date, "registrations": count} for date, count in reg_data.items()]
chart_df = pd.DataFrame(chart_data_list).sort_values("date")
if not chart_df.empty:
chart = alt.Chart(chart_df).mark_line(point=True).encode(
x=alt.X('date:T', title='Date'),
y=alt.Y('registrations:Q', title='New Registrations'),
tooltip=[alt.Tooltip('date:T', title='Date'), 'registrations:Q']
).properties(
title="Daily User Registrations"
).interactive() # Make interactive for zoom/pan
st.altair_chart(chart, use_container_width=True)
else:
st.info("No registration data available to display trend.")
else:
st.info("No user data available to display registration trend.")
# Detailed user table
st.markdown("
👥 User Details
", unsafe_allow_html=True)
with st.container(border=True): # Wrap user details section in a container
# Search and filter
col1, col2 = st.columns([2, 1])
with col1:
search_term = st.text_input(" Search users", placeholder="Search by username or email", label_visibility="visible")
with col2:
min_conversations = st.number_input("Min conversations", min_value=0, value=0, label_visibility="visible")
# 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)"): # Bold username
col1_detail, col2_detail = st.columns(2) # Renamed columns to avoid conflict
with col1_detail:
st.write(f"**Email:** {user['email']}")
st.write(f"**Joined:** {user['joined']}")
st.write(f"**Last Activity:** {user['last_activity']}")
with col2_detail:
st.write(f"**Conversations:** {user['conversations']}")
st.write(f"**Most Common Emotion:** {user['most_common_emotion'].capitalize()}") # Capitalize emotion
# Show emotion breakdown
if user['emotions_breakdown']:
st.write("**Emotion Breakdown:**")
# Convert to DataFrame for a nicer table display
emotions_df = pd.DataFrame([{"Emotion": e.capitalize(), "Count": c} for e, c in user['emotions_breakdown'].items()])
st.dataframe(emotions_df, hide_index=True, use_container_width=True)
else:
st.info("No emotion data for this user.")
st.markdown("---") # Separator for actions
# Quick actions
col1_actions, col2_actions, col3_actions = st.columns(3) # Renamed columns
with col1_actions:
if st.button(f"View Journal", key=f"view_{user['username']}", use_container_width=True):
# 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:]: # Last 5 activities
st.text_area(
f"{entry['date']} - {entry['emotion'].capitalize()} ({round(entry['confidence'] * 100)}/ confidence)",
f"User: {entry['user_input']}\nDilBot: {entry['response']}",
height=100,
disabled=True,
key=f"journal_entry_{user['username']}_{entry['date']}_{hash(entry['user_input'])}" # Unique key for text_area
)
else:
st.info("No conversations found for this user.")
with col2_actions:
reset_key = f"reset_{user['username']}"
confirm_key = f"confirm_{user['username']}"
# Use a persistent state for confirmation
if confirm_key not in st.session_state:
st.session_state[confirm_key] = False
if st.button(f"Reset Data", key=reset_key, use_container_width=True):
st.session_state[confirm_key] = True # Flag to show confirmation
st.rerun() # Rerun to display confirmation message/buttons
if st.session_state.get(confirm_key, False):
st.warning(f"Are you sure you want to reset ALL data for {user['username']}? This action is irreversible.")
col_confirm_yes, col_confirm_no = st.columns(2)
with col_confirm_yes:
if st.button(f"Yes, Reset", key=f"confirm_reset_{user['username']}", use_container_width=True):
# Clear user's journal
journal_path = get_user_file_path(user['username'], "journal.json")
if os.path.exists(journal_path):
os.remove(journal_path)
# Also remove vectorstore if it exists
vectorstore_path = get_user_file_path(user['username'], "faiss_index")
if os.path.exists(vectorstore_path):
import shutil
shutil.rmtree(vectorstore_path) # Remove directory for FAISS
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()
with col_confirm_no:
if st.button(f"Cancel", key=f"cancel_reset_{user['username']}", use_container_width=True):
st.session_state[confirm_key] = False # Cancel confirmation
st.info(f"Reset for {user['username']} cancelled.")
st.rerun() # Rerun to hide confirmation buttons
#with col3_actions:
# Placeholder for other actions like "Deactivate User"
#st.button("Deactivate User (WIP)", key=f"deactivate_{user['username']}", disabled=True, use_container_width=True)
else:
st.info("No users found matching your criteria.")
# System Analytics
st.markdown("
System Analytics
", unsafe_allow_html=True)
with st.container(border=True): # Wrap system analytics in a container
col1_analytics, col2_analytics = st.columns(2) # Renamed columns
with col1_analytics:
st.markdown("
Emotion Distribution (All Users)
", unsafe_allow_html=True)
# 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(pd.DataFrame(emotion_chart_data)).mark_bar().encode(
x=alt.X('emotion:N', title='Emotion', sort='-y'), # Sort by count descending
y=alt.Y('count:Q', title='Frequency'),
color=alt.Color('emotion:N', legend=None, scale=alt.Scale(
range=['#4CAF50', '#FFC107', '#E74C3C', '#3498DB', '#9B59B6', '#1ABC9C', '#FF5733'])), # More colors
tooltip=['emotion:N', 'count:Q']
).properties(
title="Overall Emotion Distribution"
).interactive()
st.altair_chart(emotion_chart, use_container_width=True)
else:
st.info("No emotion data available for overall analysis.")
with col2_analytics:
st.markdown("
User Activity Levels
", unsafe_allow_html=True)
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()]
if activity_data:
activity_chart = alt.Chart(pd.DataFrame(activity_data)).mark_arc(outerRadius=120, innerRadius=80).encode( # Donut chart
theta=alt.Theta('users:Q'),
color=alt.Color('level:N', title="Activity Level", scale=alt.Scale(
range=['#6c757d', '#64b5f6', '#5d6dbe', '#4a5c9d'])), # Consistent color scheme
order=alt.Order('users:Q', sort='descending'),
tooltip=['level:N', 'users:Q']
).properties(
title="User Activity Distribution"
).interactive()
st.altair_chart(activity_chart, use_container_width=True)
else:
st.info("No user activity data to display.")
# Admin logs
st.markdown("
Admin Activity Logs
", unsafe_allow_html=True)
with st.container(border=True): # Wrap admin logs in a container
admin_logs = get_admin_logs()
if admin_logs:
st.subheader("Recent Admin Activities (Last 10)")
# Display logs in a more readable format, perhaps a table or structured text
for log_entry in reversed(admin_logs[-10:]): # Last 10 activities, reversed to show newest first
timestamp = datetime.datetime.fromisoformat(log_entry["timestamp"])
st.markdown(f"**{timestamp.strftime('%Y-%m-%d %H:%M:%S')}** - **{log_entry['action']}**: `{log_entry['details']}`")
st.markdown("---") # Small separator
else:
st.info("No admin activities logged yet.")
# Data Export and Admin Log Clearing
st.markdown("
Data Management
", unsafe_allow_html=True)
with st.container(border=True): # Wrap data export in a container
col1_export, col2_export = st.columns(2) # Renamed columns
with col1_export:
st.markdown("
Export Application Data
", unsafe_allow_html=True)
if st.button("Export All Data (JSON)", key="export_data_btn", use_container_width=True):
export_data = {
"export_timestamp": str(datetime.datetime.now()),
"statistics": stats,
"admin_logs": admin_logs
}
st.download_button(
label="Download Exported Data",
data=json.dumps(export_data, indent=4),
file_name=f"dilbot_admin_export_{datetime.date.today().isoformat()}.json",
mime="application/json",
key="download_export_btn",
use_container_width=True
)
log_admin_activity("Data Export", "Initiated data export")
st.success("Data export ready for download!")
with col2_export:
st.markdown("
Clear Admin Activity Logs
", unsafe_allow_html=True)
# Add a confirmation step for clearing logs
clear_log_confirm_key = "clear_log_confirm"
if clear_log_confirm_key not in st.session_state:
st.session_state[clear_log_confirm_key] = False
if st.button("Clear Admin Logs", key="clear_admin_logs_btn", use_container_width=True):
st.session_state[clear_log_confirm_key] = True
st.rerun()
if st.session_state.get(clear_log_confirm_key, False):
st.warning("Are you sure you want to clear ALL admin logs? This action is irreversible.")
col_clear_yes, col_clear_no = st.columns(2)
with col_clear_yes:
if st.button("Yes, Clear Logs", key="confirm_clear_logs_btn", use_container_width=True):
admin_log_path = "data/admin_log.json"
if os.path.exists(admin_log_path):
os.remove(admin_log_path)
st.success("Admin logs cleared successfully!")
log_admin_activity("Admin Logs Cleared", "All admin activity logs were cleared")
st.session_state[clear_log_confirm_key] = False
st.rerun()
with col_clear_no:
if st.button("Cancel", key="cancel_clear_logs_btn", use_container_width=True):
st.session_state[clear_log_confirm_key] = False
st.info("Clearing admin logs cancelled.")
st.rerun()
st.markdown("---")
# Back to main app button (aligned left in a container)
st.markdown("
DilBot Admin Panel | Built by Members of CSG Hackathon Team
", unsafe_allow_html=True)
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)}"
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")
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."]
}
# --- CUSTOM CSS ---
st.markdown("""
""", unsafe_allow_html=True)
# --- Streamlit UI Components ---
# Header with logout button
col1, col2 = st.columns([4, 1])
with col1:
st.markdown(f"
🧠 DilBot
", unsafe_allow_html=True)
st.markdown(f"
Welcome back, {username}! Your personal emotional AI companion
", unsafe_allow_html=True)
with col2:
st.markdown("", unsafe_allow_html=True) # Spacer
if st.button("Logout", key="logout_btn", use_container_width=True):
st.session_state.authenticated = False
st.session_state.username = None
st.rerun()
# Input section wrapped in a styled container
st.markdown("
Input Your Thoughts
", unsafe_allow_html=True)
with st.container(border=True): # Streamlit's built-in container with border
col1, col2 = st.columns(2)
with col1:
# Reverting to direct st.selectbox with a visible label
st.markdown(" **Choose a quote theme:**") # Custom label for the selectbox
selected_category = st.selectbox("Select Quote Theme", list(quote_categories.keys()), label_visibility="collapsed") # Original label hidden
# The custom markdown above provides the visual label, while the st.selectbox's internal label is hidden.
with col2:
st.markdown("
Custom Quotes & Voice Input
", unsafe_allow_html=True)
# Ensure file uploader labels are visible
uploaded_quotes = st.file_uploader("Upload your own quotes (.txt)", type=["txt"], key="quote_uploader")
uploaded_audio = st.file_uploader("Upload a voice message (.wav)", type=["wav"], key="audio_uploader")
# Voice transcription button
if uploaded_audio and st.button(" Transcribe Voice Message", key="transcribe_btn", use_container_width=True):
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!")
# 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
# Input area for user message
st.markdown("
What's on your mind?
", unsafe_allow_html=True)
user_input = st.text_area(
"Share your thoughts, feelings, or experiences...",
value=st.session_state.transcribed_text,
height=180, # Increased height for more typing space
placeholder="Type here or use your transcribed voice message...",
)
# Re-adding explicit label for text area if needed, to be safe
# st.markdown("", unsafe_allow_html=True)
final_input = user_input.strip() or st.session_state.transcribed_text.strip()
# Main interaction button
if st.button("🧠 Talk to DilBot", type="primary", use_container_width=True):
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 (ORIGINAL LOGIC - NO CHANGE)
similar_docs = vectorstore.similarity_search(final_input, k=2)
context = "\n".join([doc.page_content for doc in similar_docs])
# Generate response (ORIGINAL LOGIC - NO CHANGE)
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 (ORIGINAL LOGIC - NO CHANGE)
save_user_journal(username, final_input, emotion, score, response)
# Display results with new chat bubble styling
st.markdown("
DilBot's Conversation:
", unsafe_allow_html=True)
with st.container(border=True): # Container for the conversation output
# User's input presented in a chat bubble
st.markdown(f"
""",unsafe_allow_html=True
)
if is_crisis(final_input):
st.error(" Crisis detected! Please reach out to a mental health professional immediately. "
"You are not alone. Consider contacting a helpline like the National Suicide Prevention Lifeline (988 in the US) or a local emergency service.")
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}*")
st.markdown(f"""
ℹ
Quote for you: {selected_quote}
""", unsafe_allow_html=True)
# DilBot's response presented in a chat bubble
st.markdown(f"
DilBot: {response}
", unsafe_allow_html=True)
speak(response, username)
st.session_state.transcribed_text = ""
# Add a visual separator after each conversation turn (optional)
st.markdown("", unsafe_allow_html=True)
# User's personal dashboard
st.markdown("---")
st.header(" Your Personal Dashboard")
# Load user's journal
journal_data = load_user_journal(username)
if journal_data:
# Statistics
st.subheader(" Your Emotional Statistics") # Moved statistics to the top of dashboard for prominence
with st.container(border=True):
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.metric("Avg. Confidence", "N/A") # Handle case with no journal data for avg. confidence
# Mood tracker
st.subheader(" Your Daily Mood Tracker")
with st.container(border=True): # Wrap chart in a container
# 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:
df_chart = pd.DataFrame(df_data) # Use pandas DataFrame for better Altair integration
chart = alt.Chart(df_chart).mark_bar().encode(
x=alt.X('date:N', title='Date', sort=None), # Sort by date ensures correct order
y=alt.Y('count():Q', title='Frequency'),
color=alt.Color('emotion:N', title='Emotion', scale=alt.Scale(range=['#4CAF50', '#FFC107', '#E74C3C', '#3498DB', '#9B59B6', '#1ABC9C'])), # Custom colors
tooltip=['date:N', 'emotion:N', 'count():Q']
).properties(
height=350, # Slightly increased height
title="Your Emotional Journey Over Time"
).interactive() # Make chart interactive for zoom/pan
st.altair_chart(chart, use_container_width=True)
else:
st.info("No emotional data to display yet. Start interacting with DilBot!")
# Recent conversations
st.subheader("Recent Conversations")
recent_entries = journal_data[-5:] if len(journal_data) >= 5 else journal_data
with st.container(border=True): # Wrap recent conversations in a container
if recent_entries:
for i, entry in enumerate(reversed(recent_entries)):
with st.expander(f"{entry['date']} - {entry['emotion'].capitalize()} ({round(entry['confidence'] * 100)}%)"): # Round confidence for display
st.markdown(f"**You said:** {entry['user_input']}")
st.markdown(f"**DilBot replied:** {entry['response']}")
else:
st.info("No recent conversations yet. Start talking to DilBot!")
else:
st.markdown("""
Start your first conversation with DilBot to see your personal dashboard and insights!