Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 54 additions & 60 deletions TalkHeal.py
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -131,6 +140,7 @@ def get_tone_prompt():
else:
with main_area:
render_header()
render_theme_toggle()
st.markdown(f"""
<div style="text-align: center; margin: 20px 0;">
<h3>🗣️ Current Chatbot Tone: <strong>{st.session_state['selected_tone']}</strong></h3>
Expand All @@ -150,20 +160,4 @@ def get_tone_prompt():
}
setTimeout(scrollToBottom, 100);
</script>
""", unsafe_allow_html=True)

st.markdown("""
<style>
header[data-testid="stHeader"] button {
font-size: 14px !important;
border-radius: 50% !important;
padding: 0.5em !important;
width: 40px !important;
height: 40px !important;
}
header[data-testid="stHeader"] span, header[data-testid="stHeader"] div {
font-size: 16px !important;
overflow: hidden !important;
}
</style>
""", unsafe_allow_html=True)
14 changes: 9 additions & 5 deletions components/sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -426,16 +426,20 @@ def render_sidebar():
</div>
""".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)
Expand Down
69 changes: 31 additions & 38 deletions components/theme_toggle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("""
<style>
/* Theme toggle button styling - consistent with sidebar */
[data-testid="stButton"] > button[key="theme_toggle"] {
background: var(--light-transparent-bg) !important;
/* Ultra-optimized theme toggle - minimal CSS for maximum speed */
[data-testid="stButton"] > button[key="theme_toggle_optimized"] {
background: rgba(255, 255, 255, 0.15) !important;
color: var(--text-primary) !important;
border: 1px solid var(--light-transparent-border) !important;
border-radius: var(--radius) !important;
padding: 12px 16px !important;
border: 1px solid rgba(255, 255, 255, 0.2) !important;
border-radius: 8px !important;
padding: 8px 12px !important;
font-weight: 600 !important;
transition: all 0.2s ease !important;
font-family: 'Inter', sans-serif !important;
box-shadow: 0 2px 8px var(--shadow) !important;
backdrop-filter: blur(5px) !important;
white-space: nowrap !important;
text-overflow: ellipsis !important;
overflow: hidden !important;
min-height: 44px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
font-size: 0.9em !important;
line-height: 1.2 !important;
word-break: keep-all !important;
font-size: 1.2em !important;
min-height: 36px !important;
width: 100% !important;
transition: none !important;
transform: translateZ(0) !important;
will-change: auto !important;
}

[data-testid="stButton"] > button[key="theme_toggle"]:hover {
background: var(--light-transparent-bg-hover) !important;
transform: translateY(-1px) !important;
box-shadow: 0 4px 12px var(--shadow-lg) !important;
/* Instant hover feedback - no transitions */
[data-testid="stButton"] > button[key="theme_toggle_optimized"]:hover {
background: rgba(255, 255, 255, 0.25) !important;
border-color: var(--primary-color) !important;
}

/* Ensure button text doesn't wrap */
[data-testid="stButton"] > button[key="theme_toggle"] span {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
/* Instant active feedback */
[data-testid="stButton"] > button[key="theme_toggle_optimized"]:active {
background: rgba(255, 255, 255, 0.35) !important;
}
</style>
""", unsafe_allow_html=True)
46 changes: 43 additions & 3 deletions core/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

# 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()
32 changes: 32 additions & 0 deletions core/utils.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Loading