diff --git a/TalkHeal.py b/TalkHeal.py
index 785d7e1..a4620b0 100644
--- a/TalkHeal.py
+++ b/TalkHeal.py
@@ -1,72 +1,81 @@
+
import streamlit as st
-from auth.auth_utils import init_db
-from components.login_page import show_login_page
+import streamlit_authenticator as stauth
+import time
+from auth.auth_utils import init_db, register_user, authenticate_user
st.set_page_config(page_title="TalkHeal", page_icon="💬", layout="wide")
-# --- DB Initialization ---
if "db_initialized" not in st.session_state:
init_db()
st.session_state["db_initialized"] = True
-# --- Auth State Initialization ---
if "authenticated" not in st.session_state:
st.session_state.authenticated = False
if "show_signup" not in st.session_state:
st.session_state.show_signup = False
-# --- LOGIN PAGE ---
-if not st.session_state.authenticated:
- show_login_page()
- st.stop()
-
-# --- TOP RIGHT BUTTONS: THEME TOGGLE & LOGOUT ---
-if st.session_state.get("authenticated", False):
- col_spacer, col_theme, col_logout = st.columns([5, 0.5, 0.7])
- with col_spacer:
- pass # empty spacer to push buttons right
- with col_theme:
- is_dark = st.session_state.get('dark_mode', False)
- if st.button("🌙" if is_dark else "☀️", key="top_theme_toggle", help="Toggle Light/Dark Mode", use_container_width=True):
- st.session_state.dark_mode = not is_dark
- st.session_state.theme_changed = True
+def show_login_ui():
+ st.subheader("🔐 Login")
+ email = st.text_input("Email", key="login_email")
+ password = st.text_input("Password", type="password", key="login_password")
+ if st.button("Login"):
+ success, user = authenticate_user(email, password)
+ if success:
+ st.session_state.authenticated = True
+ # Set user_email and user_name separately for journaling page access
+ st.session_state.user_email = user["email"]
+ st.session_state.user_name = user["name"]
st.rerun()
- with col_logout:
- if st.button("Logout", key="logout_btn", use_container_width=True):
- for key in ["authenticated", "user_email", "user_name", "show_signup"]:
- if key in st.session_state:
- del st.session_state[key]
+ else:
+ st.warning("Invalid email or password.")
+ st.markdown("Don't have an account? [Sign up](#)", unsafe_allow_html=True)
+ if st.button("Go to Sign Up"):
+ st.session_state.show_signup = True
+ st.rerun()
+
+def show_signup_ui():
+ st.subheader("📝 Sign Up")
+ name = st.text_input("Name", key="signup_name")
+ email = st.text_input("Email", key="signup_email")
+ password = st.text_input("Password", type="password", key="signup_password")
+ if st.button("Sign Up"):
+ success, message = register_user(name, email, password)
+ if success:
+ st.success("Account created! Please log in.")
+ st.session_state.show_signup = False
st.rerun()
+ else:
+ st.error(message)
-# --- MAIN UI (only after login) ---
-header_col1, header_col2, header_col3 = st.columns([6, 1, 1])
-with header_col1:
- st.title(f"Welcome to TalkHeal, {st.session_state.user_name}! 💬")
- st.markdown("Navigate to other pages from the sidebar.")
+# --- PERFORMANCE OPTIMIZED IMPORTS ---
-import google.generativeai as genai
-from core.utils import save_conversations, load_conversations
-from core.config import configure_gemini, PAGE_CONFIG
-from core.utils import get_current_time, create_new_conversation
+from core.utils import (
+ get_ai_response,
+ save_conversations,
+ load_conversations,
+ create_new_conversation
+)
from css.styles import apply_custom_css
-from components.header import render_header
+from core.config import configure_gemini
+from components.profile import apply_global_font_size
from components.sidebar import render_sidebar
-from components.chat_interface import render_chat_interface, handle_chat_input
+from components.header import render_header
+from components.theme_toggle import render_theme_toggle
from components.emergency_page import render_emergency_page
-from components.profile import apply_global_font_size
+from components.chat_interface import render_chat_interface, handle_chat_input
+# --- Apply CSS and configure ---
+apply_custom_css()
-# --- 1. INITIALIZE SESSION STATE ---
-if "chat_history" not in st.session_state:
- st.session_state.chat_history = []
+# Initialize required session state variables
if "conversations" not in st.session_state:
- st.session_state.conversations = load_conversations()
+ st.session_state.conversations = []
if "active_conversation" not in st.session_state:
st.session_state.active_conversation = -1
-if "show_emergency_page" not in st.session_state:
- st.session_state.show_emergency_page = False
-if "sidebar_state" not in st.session_state:
- st.session_state.sidebar_state = "expanded"
+
+if "palette_name" not in st.session_state:
+ st.session_state.palette_name = "Light"
if "mental_disorders" not in st.session_state:
st.session_state.mental_disorders = [
"Depression & Mood Disorders", "Anxiety & Panic Disorders", "Bipolar Disorder",
@@ -131,6 +140,7 @@ def get_tone_prompt():
else:
with main_area:
render_header()
+ render_theme_toggle()
st.markdown(f"""
🗣️ Current Chatbot Tone: {st.session_state['selected_tone']}
@@ -150,20 +160,4 @@ def get_tone_prompt():
}
setTimeout(scrollToBottom, 100);
-""", unsafe_allow_html=True)
-
-st.markdown("""
-
""", unsafe_allow_html=True)
\ No newline at end of file
diff --git a/components/sidebar.py b/components/sidebar.py
index 462617e..199511e 100644
--- a/components/sidebar.py
+++ b/components/sidebar.py
@@ -426,16 +426,20 @@ def render_sidebar():
""".format(current_theme['name']), unsafe_allow_html=True)
- # Theme toggle button with better styling
- button_text = "🌙 Dark Mode" if not is_dark else "☀️ Light Mode"
- button_color = "primary" if not is_dark else "secondary"
+ # Theme toggle button with ultra-fast performance
+ button_text = "🌙 Dark" if not is_dark else "☀️ Light"
+
+ # Use a unique key that includes current state and version for optimal performance
+ sidebar_button_key = f"sidebar_theme_toggle_{is_dark}_{st.session_state.get('sidebar_theme_version', 0)}"
if st.button(
button_text,
- key="sidebar_theme_toggle",
+ key=sidebar_button_key,
use_container_width=True,
- type=button_color
+ help="Toggle Light/Dark Mode"
):
+ # Increment version for faster re-renders
+ st.session_state.sidebar_theme_version = st.session_state.get('sidebar_theme_version', 0) + 1
toggle_theme()
# Quizzes expander (no longer contains nested expander)
diff --git a/components/theme_toggle.py b/components/theme_toggle.py
index e4e2dd1..95e0437 100644
--- a/components/theme_toggle.py
+++ b/components/theme_toggle.py
@@ -2,67 +2,60 @@
from core.theme import toggle_theme, get_current_theme
def render_theme_toggle():
- """Render the theme toggle button in the top right corner."""
+ """Render the theme toggle button with maximum performance optimization."""
current_theme = get_current_theme()
is_dark = current_theme["name"] == "Dark"
- # Create a container for the theme toggle
+ # Create a container for the theme toggle with minimal re-rendering
with st.container():
# Use columns to position the toggle on the right
col1, col2, col3 = st.columns([0.7, 0.2, 0.1])
with col3:
- # Theme toggle button
- button_text = "🌙 Dark Mode" if is_dark else "☀️ Light Mode"
- button_color = "primary" if is_dark else "secondary"
+ # Optimized button text with minimal emoji processing
+ button_text = "🌙" if not is_dark else "☀️"
+ # Use static key to prevent unnecessary widget recreations
+ button_key = "theme_toggle_optimized"
+
+ # Ultra-fast button with minimal state management
if st.button(
button_text,
- key="theme_toggle",
+ key=button_key,
help="Toggle Light/Dark Mode",
- use_container_width=True,
- type=button_color
+ use_container_width=True
):
+ # Direct theme toggle without version tracking
toggle_theme()
- # Add some custom CSS to style the toggle button
+ # Minimal CSS with hardware acceleration for maximum performance
st.markdown("""
""", unsafe_allow_html=True)
\ No newline at end of file
diff --git a/core/theme.py b/core/theme.py
index a52edc3..04fdc2c 100644
--- a/core/theme.py
+++ b/core/theme.py
@@ -237,13 +237,53 @@ def get_current_theme() -> Dict[str, Any]:
return palette
def set_palette(palette_name: str):
+ # Add light throttling to prevent rapid successive calls
+ import time
+ current_time = time.time()
+ last_palette_time = st.session_state.get("last_palette_change", 0)
+
+ # Only allow palette change every 50ms to prevent spam (reduced from 200ms)
+ if current_time - last_palette_time < 0.05:
+ return
+
+ if st.session_state.get("palette_name") == palette_name:
+ return # No change needed
+
+ st.session_state.last_palette_change = current_time
st.session_state.palette_name = palette_name
st.session_state.theme_changed = True
+
+ # Force immediate rerun for faster response
st.rerun()
def toggle_theme():
- """Toggle between light and dark themes."""
+ """Toggle between light and dark themes with maximum performance optimization."""
initialize_theme_state()
+
+ # Optimized throttling - reduced to 20ms for faster response while preventing spam
+ import time
+ current_time = time.time()
+ last_toggle_time = st.session_state.get("last_theme_toggle", 0)
+
+ # Minimal throttling to prevent accidental double-clicks only
+ if current_time - last_toggle_time < 0.02:
+ return
+
+ st.session_state.last_theme_toggle = current_time
+
+ # Direct state change without extra flags
st.session_state.dark_mode = not st.session_state.dark_mode
- st.session_state.theme_changed = True
- st.rerun()
\ No newline at end of file
+
+ # Mark that CSS needs to be reloaded
+ st.session_state.force_css_reload = True
+
+ # Optimized rerun - use fastest available method
+ try:
+ # Try the newer, faster rerun method first
+ import streamlit.runtime.scriptrunner.script_runner as sr
+ sr.get_script_run_ctx().request_rerun()
+ except (AttributeError, ImportError):
+ try:
+ st.experimental_rerun()
+ except AttributeError:
+ st.rerun()
\ No newline at end of file
diff --git a/core/utils.py b/core/utils.py
index 97f3a0a..238d961 100644
--- a/core/utils.py
+++ b/core/utils.py
@@ -1,3 +1,35 @@
+import json
+import os
+
+# Directory to store conversation files
+CONVERSATIONS_DIR = "conversations"
+if not os.path.exists(CONVERSATIONS_DIR):
+ os.makedirs(CONVERSATIONS_DIR)
+
+def save_conversations(username, messages):
+ """Saves a user's conversation history to a JSON file."""
+ if not username:
+ return
+ file_path = os.path.join(CONVERSATIONS_DIR, f"{username}_chat.json")
+ try:
+ with open(file_path, "w") as f:
+ json.dump(messages, f, indent=4)
+ except Exception as e:
+ print(f"Error saving conversation for {username}: {e}")
+
+def load_conversations(username):
+ """Loads a user's conversation history from a JSON file."""
+ if not username:
+ return []
+ file_path = os.path.join(CONVERSATIONS_DIR, f"{username}_chat.json")
+ if os.path.exists(file_path):
+ try:
+ with open(file_path, "r") as f:
+ return json.load(f)
+ except (json.JSONDecodeError, Exception) as e:
+ print(f"Error loading conversation for {username}: {e}")
+ return []
+ return []
from datetime import datetime, timedelta, timezone
import streamlit as st
import re
diff --git a/css/styles.py b/css/styles.py
index 18ec9cd..d4b9340 100644
--- a/css/styles.py
+++ b/css/styles.py
@@ -1,14 +1,37 @@
import streamlit as st
import base64
+import hashlib
def get_base64_of_bin_file(bin_file):
with open(bin_file, 'rb') as f:
data = f.read()
return base64.b64encode(data).decode()
+@st.cache_data(ttl=300, show_spinner=False) # Cache for 5 minutes, faster invalidation
+def _generate_css_hash(theme_config):
+ """Generate a hash of the theme configuration for caching."""
+ # Only hash essential theme properties for better cache hits
+ essential_props = ['name', 'primary', 'background_image', 'text_primary']
+ theme_subset = {k: v for k, v in theme_config.items() if k in essential_props}
+ theme_str = str(sorted(theme_subset.items()))
+ return hashlib.md5(theme_str.encode()).hexdigest()
+
def apply_custom_css():
+ """Apply CSS with performance optimizations to reduce theme toggle delays."""
from core.theme import get_current_theme
theme_config = get_current_theme()
+
+ # Ultra-fast CSS change detection
+ css_hash = _generate_css_hash(theme_config)
+ last_css_hash = st.session_state.get("last_css_hash", "")
+
+ # Skip CSS regeneration if theme hasn't changed (major performance boost)
+ if css_hash == last_css_hash and not st.session_state.get("force_css_reload", False):
+ return
+
+ # Update cache state with minimal overhead
+ st.session_state.last_css_hash = css_hash
+ st.session_state.force_css_reload = False
theme_overrides = {
'primary': '#6366f1',
'primary_light': '#818cf8',
@@ -56,11 +79,15 @@ def apply_custom_css():
--border-light: {theme_config['border_light']};
--shadow: {theme_config['shadow']};
--shadow-lg: {theme_config['shadow_lg']};
+ --light-transparent-bg: {theme_config['light_transparent_bg']};
+ --light-transparent-bg-hover: {theme_config['light_transparent_bg_hover']};
+ --light-transparent-border: {theme_config['light_transparent_border']};
+ --button-hover-text: {theme_config['primary_dark']};
--radius: 12px;
--radius-lg: 22px;
--radius-xl: 36px;
--glass-effect: linear-gradient(135deg, rgba(255,255,255,0.10), rgba(255,255,255,0.05));
- --transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+ --transition: all 0.1s ease-out;
}}
.stApp > header {{
@@ -126,10 +153,9 @@ def apply_custom_css():
font-weight: 470;
line-height: 1.5;
position: relative;
- backdrop-filter: blur(11px);
display: block;
- opacity: 0;
- animation: floatUp 0.5s cubic-bezier(0.25,0.8,0.25,1) forwards;
+ opacity: 1;
+ transform: none;
}}
/* User message styling */
@@ -151,10 +177,10 @@ def apply_custom_css():
margin-right: auto;
}}
- /* Message animation */
+ /* Simplified message animation for better performance */
@keyframes floatUp {{
- from {{ transform: translateY(20px); opacity: 0; }}
- to {{ transform: none; opacity: 1; }}
+ from {{ opacity: 0.7; }}
+ to {{ opacity: 1; }}
}}
/* Message timestamp styling */
@@ -169,7 +195,7 @@ def apply_custom_css():
color: #c4d0e0;
}}
- /* Welcome message styling */
+ /* Welcome message styling - performance optimized */
.welcome-message {{
background: linear-gradient(120deg, rgba(99,102,241,0.75) 0%, rgba(236,72,153,0.75) 100%);
color: #f7fafb;
@@ -177,24 +203,22 @@ def apply_custom_css():
margin: 38px auto 32px auto;
border-radius: var(--radius-xl);
max-width: 680px;
- box-shadow: 0 6px 38px 0 rgba(96,100,255,0.11);
+ box-shadow: 0 4px 24px 0 rgba(96,100,255,0.08);
font-size: 1.14em;
text-align: center;
- backdrop-filter: blur(12px);
position: relative;
overflow: hidden;
- transform: scale(0.97);
- opacity: 0;
- animation: scaleIn 0.7s cubic-bezier(0.22,0.68,0.32,1.18) .1s forwards;
+ opacity: 1;
+ transform: scale(1);
}}
- /* Welcome message animation */
+ /* Simplified animation for better performance */
@keyframes scaleIn {{
- from {{ transform: scale(0.97); opacity: 0; }}
- to {{ transform: scale(1); opacity: 1; }}
+ from {{ opacity: 0.8; }}
+ to {{ opacity: 1; }}
}}
- /* Main header styling */
+ /* Header styling - performance optimized */
.main-header {{
--gradient: linear-gradient(100deg, var(--primary-color), var(--secondary-color));
text-align: center;
@@ -207,21 +231,13 @@ def apply_custom_css():
border: 1px solid var(--border-light);
position: relative;
overflow: hidden;
- backdrop-filter: blur(10px);
}}
- /* Header gradient animation */
+ /* Simplified header accent - no animation for better performance */
.main-header::before {{
content: '';
position: absolute; top: 0; left: 0; right: 0; height: 3px;
background: var(--gradient);
- animation: gradientFlow 7s linear infinite;
- background-size: 200% 200%;
- }}
- @keyframes gradientFlow {{
- 0% {{ background-position: 0% 50%; }}
- 50% {{ background-position: 100% 50%;}}
- 100% {{ background-position: 0% 50%;}}
}}
/* Header title styling */
@@ -261,7 +277,6 @@ def apply_custom_css():
font-weight: 600;
font-size: 1.1em;
border: 1px solid rgba(239, 68, 68, 0.8) !important;
- backdrop-filter: blur(5px);
text-decoration: none !important;
position: relative;
overflow: hidden;
@@ -302,11 +317,10 @@ def apply_custom_css():
/* Sidebar styling */
[data-testid="stSidebar"] {{
background: linear-gradient(120deg, rgba(236,72,153,0.45), rgba(219,39,119,0.25), rgba(236,72,153,0.15)) !important;
- backdrop-filter: blur(15px) !important;
border-right: 2px solid rgba(236,72,153,0.35) !important;
- box-shadow: 8px 0 48px rgba(236,72,153,0.25) !important;
+ box-shadow: 4px 0 24px rgba(236,72,153,0.15) !important;
color: #e2e8f0 !important;
- transition: background .32s cubic-bezier(.5,.13,.36,1.19);
+ transition: background .2s ease-out;
}}
[data-testid="stSidebar"] * {{
color: #f5f7fb !important;
@@ -327,8 +341,7 @@ def apply_custom_css():
height: 40px !important;
font-size: 20px !important;
font-weight: 900 !important;
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3) !important;
- backdrop-filter: blur(10px) !important;
+ box-shadow: 0 3px 12px rgba(0, 0, 0, 0.2) !important;
}}
/* Sidebar toggle button hover effects */
@@ -378,53 +391,79 @@ def apply_custom_css():
box-shadow: 0 6px 16px rgba(99, 102, 241, 0.4) !important;
}}
- /* Theme toggle button styling */
- .stApp button[key="theme_toggle"],
- .stApp button[key="sidebar_theme_toggle"] {{
+ /* Theme toggle button styling - ultra-fast optimized */
+ .stApp button[key*="theme_toggle"] {{
white-space: nowrap !important;
text-overflow: ellipsis !important;
overflow: hidden !important;
- min-height: 44px !important;
+ min-height: 40px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
- font-size: 0.9em !important;
- line-height: 1.2 !important;
+ font-size: 0.85em !important;
+ line-height: 1.1 !important;
word-break: keep-all !important;
background: var(--light-transparent-bg) !important;
color: var(--text-primary) !important;
border: 1px solid var(--light-transparent-border) !important;
border-radius: var(--radius) !important;
- padding: 12px 16px !important;
+ padding: 10px 14px !important;
font-weight: 600 !important;
- transition: all 0.2s ease !important;
+ transition: all 0.1s ease-out !important;
font-family: 'Inter', sans-serif !important;
- box-shadow: 0 2px 8px var(--shadow) !important;
- backdrop-filter: blur(5px) !important;
+ box-shadow: 0 2px 6px var(--shadow) !important;
+ backdrop-filter: blur(3px) !important;
+ will-change: transform, background, box-shadow !important;
+ transform: translateZ(0) !important; /* Force hardware acceleration */
+ backface-visibility: hidden !important; /* Prevent flickering */
+ }}
+
+ .stApp button[key*="theme_toggle"]:hover {{
+ background: var(--light-transparent-bg-hover) !important;
+ color: var(--button-hover-text) !important;
+ border-color: var(--primary-color) !important;
+ border-width: 2px !important;
+ transform: translateY(-1px) scale(1.02) translateZ(0) !important;
+ box-shadow: 0 4px 10px var(--shadow-lg) !important;
}}
- /* General button styling */
+ .stApp button[key*="theme_toggle"]:active {{
+ transform: translateY(0) scale(1.0) translateZ(0) !important;
+ transition: all 0.05s ease-out !important;
+ }}
+
+ /* General button styling - optimized for speed */
button, .stButton > button, .stDownloadButton > button, .stFormSubmitButton > button {{
background: var(--glass-effect) !important;
background-color: var(--light-transparent-bg, rgba(255,255,255,0.13)) !important;
- color: #000000 !important;
+ color: var(--text-primary) !important;
border: 1px solid var(--light-transparent-border, rgba(255,255,255,0.16)) !important;
border-radius: var(--radius) !important;
padding: 14px 22px !important;
font-weight: 600 !important;
font-family: 'Poppins',sans-serif !important;
- box-shadow: 0 3px 12px rgba(0,0,0,0.08) !important;
- transition: var(--transition,.21s cubic-bezier(.5,.08,.37,1.11)) !important;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05) !important;
+ transition: all 0.05s ease-out !important;
+ transform: translateZ(0) !important;
}}
- /* General button hover effects */
+ /* General button hover effects - instant feedback */
button:hover, .stButton > button:hover,
.stDownloadButton > button:hover,
.stFormSubmitButton > button:hover {{
border-color: var(--primary-color) !important;
border-width: 2px !important;
- transform: translateY(-2px) scale(1.04) !important;
- box-shadow: 0 4px 16px rgba(99,102,241,0.2) !important;
+ color: var(--button-hover-text) !important;
+ transform: translateZ(0) !important;
+ box-shadow: 0 3px 10px rgba(99,102,241,0.15) !important;
+ }}
+
+ /* Button active state for instant feedback */
+ button:active, .stButton > button:active,
+ .stDownloadButton > button:active,
+ .stFormSubmitButton > button:active {{
+ transform: translateZ(0) !important;
+ transition: all 0.02s ease-out !important;
}}
/* Primary buttons */
@@ -508,7 +547,7 @@ def apply_custom_css():
opacity: .77 !important;
}}
- /* Floating action button styling */
+ /* Floating action button styling - optimized */
.floating-action-button {{
position: fixed;
bottom: 28px; right: 28px;
@@ -518,16 +557,16 @@ def apply_custom_css():
color: #fff;
display: flex; align-items: center; justify-content: center;
font-size: 23px;
- box-shadow: 0 6px 24px rgba(0,0,0,0.14);
+ box-shadow: 0 4px 16px rgba(0,0,0,0.1);
cursor: pointer; z-index: 120;
border: none;
- transition: .16s cubic-bezier(.47,.43,.41,1.35);
+ transition: transform 0.1s ease-out;
}}
- /* Floating action button hover effects */
+ /* Floating action button hover effects - reduced animation */
.floating-action-button:hover {{
- transform: translateY(-4px) scale(1.10);
- box-shadow: 0 10px 32px rgba(0,0,0,0.27);
+ transform: translateY(-2px) scale(1.05);
+ box-shadow: 0 6px 20px rgba(0,0,0,0.2);
}}
/* Expander styling */