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( """ """, 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("
", unsafe_allow_html=True) # First Image: Users vast amount satisfied (https://cdn-icons-png.flaticon.com/512/33/33308.png) st.markdown(f"""

Users Vastly Satisfied

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"

👑 DilBot Admin Dashboard

", unsafe_allow_html=True) st.markdown("

Monitor users, conversations, and app analytics

", unsafe_allow_html=True) with col2: st.markdown("
", unsafe_allow_html=True) # Spacer if st.button("Logout", key="admin_logout", use_container_width=True): st.session_state.authenticated = False st.session_state.username = None st.session_state.is_admin = False st.rerun() # Log admin access log_admin_activity("Dashboard Access", "Viewed admin dashboard") # Get statistics stats = get_admin_stats() # Overview metrics st.markdown("

Overview

", 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("", 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"
You: {final_input}
", unsafe_allow_html=True) #st.success(f"**Emotion Detected:** {emotion.capitalize()} ({round(score*100)}/ confidence)") st.markdown( f"""

Emotion Detected: {emotion.capitalize()} ({round(score*100)}% confidence)

""",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!

""", unsafe_allow_html=True) st.markdown("---") st.markdown("", unsafe_allow_html=True) # Main app logic def main(): if not st.session_state.authenticated: show_auth_page() elif st.session_state.is_admin: show_admin_dashboard() else: show_main_app() if __name__ == "__main__": main()