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
157 changes: 115 additions & 42 deletions TalkHeal.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,50 +43,122 @@ def show_signup_ui():
st.session_state.show_signup = False
st.rerun()
else:
st.error(message)
st.markdown("Already have an account? [Login](#)", unsafe_allow_html=True)
if st.button("Go to Login"):
st.session_state.show_signup = False
st.rerun()

if not st.session_state.authenticated:
if st.session_state.show_signup:
show_signup_ui()
else:
show_login_ui()
else:
st.title(f"Welcome to TalkHeal, {st.session_state.user_name}! 💬")
st.markdown("Navigate to other pages from the sidebar.")
import streamlit as st
import streamlit_authenticator as stauth
import time
from core.utils import (
load_auth_config,
load_css,
apply_custom_css,
load_google_fonts,
get_ai_response,
save_conversations,
load_conversations
)
from components.sidebar import render_sidebar
from components.header import render_header

if st.button("Logout"):
for key in ["authenticated", "user_email", "user_name", "show_signup"]:
if key in st.session_state:
del st.session_state[key]
st.rerun()
# --- Page and Session Initialization ---
st.set_page_config(
page_title="TalkHeal",
page_icon="favicon/favicon.ico",
layout="wide",
initial_sidebar_state="expanded"
)

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 css.styles import apply_custom_css
from components.header import render_header
from components.sidebar import render_sidebar
from components.chat_interface import render_chat_interface, handle_chat_input
from components.emergency_page import render_emergency_page
from components.profile import apply_global_font_size


# --- 1. INITIALIZE SESSION STATE ---
if "chat_history" not in st.session_state:
st.session_state.chat_history = []
if "conversations" not in st.session_state:
st.session_state.conversations = load_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"
load_css()
apply_custom_css()
load_google_fonts()

# --- Authentication ---
if 'authenticator' not in st.session_state:
config = load_auth_config()
st.session_state.authenticator = stauth.Authenticate(
config['credentials'],
config['cookie']['name'],
config['cookie']['key'],
config['cookie']['expiry_days'],
config['preauthorized']
)

authenticator = st.session_state.authenticator
name, authentication_status, username = authenticator.login('main')

# --- Session State for Chat ---
if "messages" not in st.session_state:
st.session_state.messages = []

# --- Main Application Logic ---
if authentication_status:
# Load user's conversation history once per session after login
if "conversations_loaded" not in st.session_state and username:
st.session_state.messages = load_conversations(username)
st.session_state.conversations_loaded = True

# Main app layout
col1, col2 = st.columns([1, 4])

with col1:
render_sidebar(authenticator)

with col2:
render_header()

# Display existing chat messages from session state
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])

# Handle new chat input from the user
if prompt := st.chat_input("Share your thoughts..."):
# Add user message to session state and display it
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)

# Generate and display AI response
with st.chat_message("assistant"):
with st.spinner("TalkHeal is thinking..."):
# Define the system prompt for the AI
system_prompt = (
"You are TalkHeal, a compassionate and supportive AI mental health companion. "
"Your goal is to provide a safe, non-judgmental space for users to share their thoughts and feelings. "
"Respond with empathy, offer encouragement, and gently guide the conversation. "
"Do not provide medical advice, diagnoses, or treatment plans. "
"If a user seems to be in crisis, strongly encourage them to seek help from a professional."
)

# Create a formatted history for the AI prompt
chat_history_for_prompt = "\n".join(
[f'{m["role"]}: {m["content"]}' for m in st.session_state.messages]
)

try:
# Call the AI model
ai_response = get_ai_response(
f"{system_prompt}\n\n{chat_history_for_prompt}\nuser: {prompt}\nassistant:",
"gemini-1.5-flash"
)

# Display the AI response
st.markdown(ai_response)

# Add AI response to session state
st.session_state.messages.append({"role": "assistant", "content": ai_response})

# Save the updated conversation history
if username:
save_conversations(username, st.session_state.messages)

except Exception as e:
error_message = f"Sorry, an error occurred: {e}"
st.error(error_message)
# Add an error message to the chat for user visibility
st.session_state.messages.append({"role": "assistant", "content": "I'm having trouble responding right now. Please try again."})

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 @@ -151,6 +223,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 @@ -170,4 +243,4 @@ def get_tone_prompt():
}
setTimeout(scrollToBottom, 100);
</script>
""", unsafe_allow_html=True)
""", 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 @@ -347,16 +347,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
59 changes: 38 additions & 21 deletions components/theme_toggle.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
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 optimized performance."""
current_theme = get_current_theme()
is_dark = current_theme["name"] == "Dark"

Expand All @@ -12,54 +12,71 @@ def render_theme_toggle():
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"
# Theme toggle button with ultra-fast response
button_text = "🌙 Dark" if not is_dark else "☀️ Light"

# Use a unique key that includes current state to prevent conflicts
button_key = f"theme_toggle_{is_dark}_{st.session_state.get('theme_version', 0)}"

# Add immediate visual feedback with custom styling
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
):
# Increment theme version for faster key generation
st.session_state.theme_version = st.session_state.get('theme_version', 0) + 1
toggle_theme()

# Add some custom CSS to style the toggle button
# Ultra-optimized CSS with hardware acceleration
st.markdown("""
<style>
/* Theme toggle button styling - consistent with sidebar */
[data-testid="stButton"] > button[key="theme_toggle"] {
/* Theme toggle button styling - maximum performance */
[data-testid="stButton"] > button[key*="theme_toggle"] {
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.08s 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(2px) !important;
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;
will-change: transform, background, box-shadow !important;
transform: translateZ(0) !important;
backface-visibility: hidden !important;
-webkit-font-smoothing: antialiased !important;
-moz-osx-font-smoothing: grayscale !important;
}

[data-testid="stButton"] > button[key="theme_toggle"]:hover {
[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;
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;
}

[data-testid="stButton"] > button[key*="theme_toggle"]:active {
transform: translateY(0) scale(1.0) translateZ(0) !important;
transition: all 0.05s ease-out !important;
}

/* Ensure button text doesn't wrap */
[data-testid="stButton"] > button[key="theme_toggle"] span {
[data-testid="stButton"] > button[key*="theme_toggle"] span {
white-space: nowrap !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
Expand Down
30 changes: 29 additions & 1 deletion core/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,13 +237,41 @@ 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 optimized performance."""
initialize_theme_state()

# Add light throttling to prevent rapid successive calls
import time
current_time = time.time()
last_toggle_time = st.session_state.get("last_theme_toggle", 0)

# Only allow theme toggle every 50ms to prevent spam (reduced from 200ms)
if current_time - last_toggle_time < 0.05:
return

st.session_state.last_theme_toggle = current_time
st.session_state.dark_mode = not st.session_state.dark_mode
st.session_state.theme_changed = True

# Force immediate rerun for faster response
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