Spaces:
No application file
No application file
| 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 | |
| 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 | |
| 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() |