diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..8d75d76
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,15 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: #
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+polar: # Replace with a single Polar username
+buy_me_a_coffee: mreccentric
+thanks_dev: #
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..58c0cff
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,27 @@
+name: Bug report
+description: Create a report to help us improve
+labels: bug
+body:
+ - type: textarea
+ attributes:
+ label: Describe the bug
+ description: Describe the bug.
+ placeholder: >
+ A clear and concise description of what the bug is.
+ validations:
+ required: true
+ - type: textarea
+ attributes:
+ label: To Reproduce
+ placeholder: >
+ Steps to reproduce the behavior:
+ - type: textarea
+ attributes:
+ label: Expected behavior
+ placeholder: >
+ A clear and concise description of what you expected to happen.
+ - type: textarea
+ attributes:
+ label: Additional context
+ placeholder: >
+ Add any other context about the problem here.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..12a08a1
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,2 @@
+blank_issue_enable: true
+blank_issue_title: "Please provide a title for your issue"
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..bfe9d8d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,26 @@
+name: Feature request
+description: Suggest an idea for this project
+labels: enhancement
+body:
+ - type: textarea
+ attributes:
+ label: Is your feature request related to a problem or challenge?
+ description: Please describe what you are trying to do.
+ placeholder: >
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+ (This section helps developers understand the context and *why* for this feature, in addition to the *what*)
+ - type: textarea
+ attributes:
+ label: Describe the solution you'd like
+ placeholder: >
+ A clear and concise description of what you want to happen.
+ - type: textarea
+ attributes:
+ label: Describe alternatives you've considered
+ placeholder: >
+ A clear and concise description of any alternative solutions or features you've considered.
+ - type: textarea
+ attributes:
+ label: Additional context
+ placeholder: >
+ Add any other context or screenshots about the feature request here.
\ No newline at end of file
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..790fcf6
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,38 @@
+## Which issue does this PR close?
+
+
+
+- Closes #.
+
+## Rationale for this change
+
+
+
+## What changes are included in this PR?
+
+
+
+## Are these changes tested?
+
+
+
+## Are there any user-facing changes?
+
+
+
+
\ No newline at end of file
diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml
new file mode 100644
index 0000000..2ab0bb7
--- /dev/null
+++ b/.github/workflows/auto-author-assign.yml
@@ -0,0 +1,16 @@
+name: Auto Author Assign
+
+on:
+ pull_request_target:
+ types: [ opened, reopened ]
+
+permissions:
+ pull-requests: write
+
+jobs:
+ assign-author:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: toshimaru/auto-author-assign@v2.1.1
+ with:
+ repo-token: ${{ secrets.GH_PERSONAL_TOKEN }}
diff --git a/.github/workflows/sync-issue-labels.yml b/.github/workflows/sync-issue-labels.yml
new file mode 100644
index 0000000..5abc15d
--- /dev/null
+++ b/.github/workflows/sync-issue-labels.yml
@@ -0,0 +1,58 @@
+name: Sync Labels from Issues to Merged PRs
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 2 * * *'
+
+jobs:
+ sync-labels:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v4
+
+ - name: Install GitHub CLI & jq
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y gh jq
+
+ - name: Sync labels from linked issues to merged PRs
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ REPO: eccentriccoder01/talkheal
+ run: |
+ echo "๐ฅ Fetching merged PRs for $REPO..."
+ gh pr list --state merged --limit 100 --repo "$REPO" --json number,title,body > merged_prs.json
+
+ jq -c '.[]' merged_prs.json | while read -r pr; do
+ pr_number=$(echo "$pr" | jq -r '.number')
+ pr_title=$(echo "$pr" | jq -r '.title')
+ pr_body=$(echo "$pr" | jq -r '.body')
+
+ issue_number=$(echo "$pr_title $pr_body" | grep -oE '#[0-9]+' | head -n 1 | tr -d '#')
+
+ if [ -z "$issue_number" ]; then
+ echo "โ PR #$pr_number has no linked issue."
+ continue
+ fi
+
+ echo "๐ PR #$pr_number linked to issue #$issue_number"
+
+ issue_labels=$(gh issue view "$issue_number" --repo "$REPO" --json labels | jq -r '.labels[].name')
+
+ if [ -z "$issue_labels" ]; then
+ echo "โ ๏ธ Issue #$issue_number has no labels."
+ continue
+ fi
+
+ while IFS= read -r label; do
+ echo "๐ท๏ธ Applying label '$label' to PR #$pr_number..."
+ gh pr edit "$pr_number" --repo "$REPO" --add-label "$label"
+ done <<< "$issue_labels"
+
+ sleep 0.5
+ done
+
+ echo "โ Done syncing labels."
diff --git a/.streamlit/secrets.toml b/.streamlit/secrets.toml
new file mode 100644
index 0000000..9aa7542
--- /dev/null
+++ b/.streamlit/secrets.toml
@@ -0,0 +1 @@
+GEMINI_API_KEY = "AIzaSyDsLJgA58LvgFtnUdVBLFb08GZQV0wXYjQ"
\ No newline at end of file
diff --git a/README.md b/README.md
index a6989d3..ce5e063 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
## ๐ง Your AI-Powered Mental Health Companion
-**TalkHeal** is an empathetic, intelligent, and interactive mental health support assistant built using **Python** and **Streamlit**. Designed with compassion and care at its core, it offers 24/7 support, emotional journaling, resource guidance, and AI-powered conversations powered by Googleโs Gemini Pro.
+**TalkHeal** is an empathetic, intelligent, and interactive mental health support assistant built using **Python** and **Streamlit**. Designed with compassion and care at its core, it offers 24/7 support, emotional journaling, resource guidance, and AI-powered conversations powered by Googleโs Gemini.
---
@@ -56,29 +56,26 @@
## ๐ Project Stats
-
", unsafe_allow_html=True)
+
+ st.markdown("### ๐ Follow the animation to breathe in and out")
+ st.write("Use this simple breathing exercise to relax. Follow the circle expanding and contracting.")
+
+ circle_animation = """
+
+
+
+ """
+ st.markdown(circle_animation, unsafe_allow_html=True)
+
+ breath_text = st.empty()
+
+ if st.button("๐ Start Breathing"):
+ for _ in range(3):
+ breath_text.markdown("## ๐ฌ๏ธ Breathe In...")
+ time.sleep(4)
+ breath_text.markdown("## โ Hold...")
+ time.sleep(2)
+ breath_text.markdown("## ๐ฎโ๐จ Breathe Out...")
+ time.sleep(4)
+ breath_text.markdown("### โ Done! Feel better?")
+
+ with st.expander("๐ Need a Timer?"):
+ minutes = st.slider("How many minutes do you want to do this?", 1, 10, 2)
+ if st.button("Start Timer"):
+ st.success("Relax and follow the animation...")
+ timer_placeholder = st.empty()
+ for i in range(minutes * 60, 0, -1):
+ mins, secs = divmod(i, 60)
+ timer_text = f"{mins:02d}:{secs:02d}"
+ timer_placeholder.markdown(f"## โณ {timer_text}")
+ time.sleep(1)
+ timer_placeholder.markdown("### โ Timer complete!")
+
+if __name__ == "__main__":
+ breathing_exercise()
diff --git a/components/header.py b/components/header.py
index 43bd86f..6a1224c 100644
--- a/components/header.py
+++ b/components/header.py
@@ -2,38 +2,12 @@
def render_header():
with st.container():
- # Top bar with hamburger menu and theme toggle
- col1, col2, col3 = st.columns([0.1, 0.8, 0.1])
-
- with col1:
- if st.button("โฐ", key="top_hamburger_menu", help="Toggle Sidebar", use_container_width=True):
- if st.session_state.sidebar_state == "expanded":
- st.session_state.sidebar_state = "collapsed"
- else:
- st.session_state.sidebar_state = "expanded"
- st.rerun()
-
- with col3:
- 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
- st.rerun()
-
- st.markdown("""
-
-
TalkHeal
-
Your Mental Health Companion ๐
-
- """, unsafe_allow_html=True)
- ## Commented out this part of the header because the 'emergency button' functionality is quite similar, hence causing redundancy. ##
-
- # with st.expander("๐ Find Help Nearby"):
- # location_input = st.text_input("Enter your city", key="header_location_search")
- # if st.button("๐ Search Centers", key="header_search_nearby"):
- # if location_input:
- # search_url = f"https://www.google.com/maps/search/mental+health+centers+{location_input.replace(' ', '+')}"
- # st.markdown(f'๐บ๏ธ View Mental Health Centers Near {location_input}', unsafe_allow_html=True)
- # st.success("Opening search results in a new tab...")
- # else:
- # st.warning("Please enter a city name")
+ # Only one column for header
+ column = st.columns(1)[0]
+ with column:
+ st.markdown("""
+
+
TalkHeal
+
Your Mental Health Companion ๐
+
+ """, unsafe_allow_html=True)
diff --git a/components/login_page.py b/components/login_page.py
new file mode 100644
index 0000000..a84e209
--- /dev/null
+++ b/components/login_page.py
@@ -0,0 +1,72 @@
+import streamlit as st
+from auth.auth_utils import register_user, authenticate_user
+
+def show_login_page():
+ st.markdown(
+ """
+
+ """, unsafe_allow_html=True
+ )
+
+ # Use session state to switch between login and signup
+ if "show_signup" not in st.session_state:
+ st.session_state.show_signup = False
+
+ if st.session_state.show_signup:
+ 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! Welcome.")
+ st.session_state.authenticated = True
+ st.session_state.user_email = email
+ st.session_state.user_name = name
+ st.session_state.show_signup = False
+ st.rerun()
+ else:
+ st.error(message)
+ # Only this button for switching to login
+ if st.button("Already have an account? Login"):
+ st.session_state.show_signup = False
+ st.rerun()
+ else:
+ 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
+ st.session_state.user_email = user["email"]
+ st.session_state.user_name = user["name"]
+ st.rerun()
+ else:
+ st.warning("Invalid email or password.")
+ # Only this button for switching to signup
+ if st.button("Don't have an account? Sign up"):
+ st.session_state.show_signup = True
+ st.rerun()
\ No newline at end of file
diff --git a/components/profile.py b/components/profile.py
new file mode 100644
index 0000000..90d8943
--- /dev/null
+++ b/components/profile.py
@@ -0,0 +1,353 @@
+"""
+TalkHeal Profile Management Module
+
+This module handles user profile functionality including:
+- Profile creation and editing
+- Profile picture upload and management
+- User preferences (name, font size)
+- Profile display in sidebar
+
+Author: TalkHeal Team
+Version: 1.0
+"""
+
+import streamlit as st
+import base64
+from datetime import datetime
+from io import BytesIO
+from PIL import Image
+
+
+def initialize_profile_state():
+ """Initialize profile data in session state if not exists"""
+ if "user_profile" not in st.session_state:
+ st.session_state.user_profile = {
+ "name": "",
+ "profile_picture": None,
+ "join_date": datetime.now().strftime("%B %Y"),
+ "font_size": "Medium"
+ }
+
+
+def get_greeting():
+ """Get appropriate greeting based on current time"""
+ current_hour = datetime.now().hour
+ if current_hour < 12:
+ return "Good morning"
+ elif current_hour < 17:
+ return "Good afternoon"
+ else:
+ return "Good evening"
+
+
+def get_user_initials(name):
+ """Generate user initials from name"""
+ if name:
+ return ''.join([word[0].upper() for word in name.split()[:2]])
+ return "TH"
+
+
+def create_default_avatar(initials, size=80):
+ """Create a default avatar with user initials"""
+ return st.markdown(f"""
+
+ {initials}
+
+ """, unsafe_allow_html=True)
+
+
+def handle_profile_picture_upload(uploaded_file):
+ """Handle profile picture upload and processing"""
+ if uploaded_file is not None:
+ try:
+ # Process and resize image to medium size
+ image = Image.open(uploaded_file)
+ # Resize to medium size (200x200) while maintaining aspect ratio
+ image.thumbnail((200, 200), Image.Resampling.LANCZOS)
+
+ # Convert to base64 for storage
+ buffered = BytesIO()
+ image.save(buffered, format="PNG")
+ img_str = base64.b64encode(buffered.getvalue()).decode()
+
+ # Save to session state
+ st.session_state.user_profile["profile_picture"] = f"data:image/png;base64,{img_str}"
+
+ st.success("โ Profile picture uploaded successfully!")
+ return True
+
+ except Exception as e:
+ st.error("โ Error uploading image. Please try a different file.")
+ return False
+
+ return False
+
+
+def render_profile_header():
+ """Render the profile header with picture and greeting"""
+ profile_data = st.session_state.user_profile
+ greeting = get_greeting()
+
+ # Profile header section
+ st.markdown("### ๐ค Profile")
+
+ # Profile picture and greeting
+ col1, col2 = st.columns([1, 2])
+
+ with col1:
+ if profile_data["profile_picture"]:
+ # Display uploaded profile picture with circular shape and medium size
+ st.markdown(f"""
+
+
+
+ """, unsafe_allow_html=True)
+ else:
+ # Default avatar with initials
+ initials = get_user_initials(profile_data["name"])
+ create_default_avatar(initials, 80)
+
+ with col2:
+ if profile_data["name"]:
+ display_name = profile_data["name"].split()[0]
+ st.markdown(f"**{greeting}, {display_name}!** ๐")
+ else:
+ st.markdown(f"**Welcome to TalkHeal!** ๐")
+ st.caption(f"Member since {profile_data['join_date']}")
+
+
+def render_profile_settings():
+ """Render the profile settings form"""
+ profile_data = st.session_state.user_profile
+
+ # Add CSS to fix the text input color issue
+ st.markdown("""
+
+ """, unsafe_allow_html=True)
+
+ with st.expander("โ๏ธ Profile Settings"):
+
+ # Name input
+ st.markdown("**Your Name**")
+ new_name = st.text_input(
+ "Enter your name",
+ value=profile_data["name"],
+ key="profile_name_input",
+ placeholder="Enter your name",
+ help="Enter your preferred name for personalized interactions"
+ )
+
+ # Profile picture upload
+ st.markdown("**Profile Picture**")
+ uploaded_file = st.file_uploader(
+ "Upload a profile picture (Optional)",
+ type=['png', 'jpg', 'jpeg'],
+ key="profile_pic_upload",
+ help="Drag and drop or click to upload. Supported formats: PNG, JPG, JPEG"
+ )
+
+ # Handle file upload
+ handle_profile_picture_upload(uploaded_file)
+
+ # Font size preference
+ st.markdown("**Font Size**")
+ font_size = st.selectbox(
+ "Choose your preferred text size",
+ ["Small", "Medium", "Large"],
+ index=["Small", "Medium", "Large"].index(profile_data["font_size"]),
+ key="font_size_selector",
+ help="This will change the font size throughout the entire application"
+ )
+
+ # Action buttons row
+ col_save, col_reset = st.columns(2)
+
+ with col_save:
+ # Save profile button
+ if st.button("๐พ Save Profile", key="save_profile", use_container_width=True, type="primary"):
+ # Update profile data
+ profile_data["name"] = new_name.strip()
+ profile_data["font_size"] = font_size
+
+ # Save to session state
+ st.session_state.user_profile = profile_data
+
+ # Apply font size globally (you can implement this in your main app)
+ st.session_state.global_font_size = font_size
+
+ # Success message
+ st.success("๐ Profile saved successfully!")
+ st.balloons()
+
+ # Rerun to update the display
+ st.rerun()
+
+ with col_reset:
+ # Reset profile button
+ if st.button("๐ Reset All", key="reset_profile", use_container_width=True, type="secondary"):
+ # Show confirmation dialog
+ st.session_state.show_reset_confirmation = True
+ st.rerun()
+
+ # Reset confirmation dialog
+ if st.session_state.get("show_reset_confirmation", False):
+ st.warning("โ ๏ธ Are you sure you want to reset all profile settings?")
+ col_confirm, col_cancel = st.columns(2)
+
+ with col_confirm:
+ if st.button("โ Yes, Reset", key="confirm_reset", use_container_width=True, type="primary"):
+ # Reset to default values
+ st.session_state.user_profile = {
+ "name": "",
+ "profile_picture": None,
+ "join_date": datetime.now().strftime("%B %Y"),
+ "font_size": "Medium"
+ }
+ # Reset global font size
+ st.session_state.global_font_size = "Medium"
+ st.session_state.show_reset_confirmation = False
+ st.success("๐ Profile reset successfully!")
+ st.rerun()
+
+ with col_cancel:
+ if st.button("โ Cancel", key="cancel_reset", use_container_width=True):
+ st.session_state.show_reset_confirmation = False
+ st.rerun()
+
+
+def render_profile_stats():
+ """Render simplified profile statistics"""
+ profile_data = st.session_state.user_profile
+
+ if profile_data["name"]:
+ with st.expander("๐ Your TalkHeal Journey"):
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.metric("Conversations", "0", help="Total chat sessions")
+
+ with col2:
+ st.metric("Days Active", "1", help="Days you've used TalkHeal")
+
+
+def render_profile_section():
+ """
+ Main function to render the complete profile section
+ This is the function that should be imported and called in sidebar
+ """
+ # Initialize profile state
+ initialize_profile_state()
+
+ # Render profile components
+ render_profile_header()
+ render_profile_settings()
+ render_profile_stats()
+
+ # Add separator
+ st.markdown("---")
+
+
+# Optional: Helper functions for other parts of the app
+def get_user_name():
+ """Get the current user's name"""
+ if "user_profile" in st.session_state:
+ return st.session_state.user_profile.get("name", "")
+ return ""
+
+
+def get_user_font_size():
+ """Get the current user's preferred font size"""
+ if "global_font_size" in st.session_state:
+ return st.session_state.global_font_size
+ elif "user_profile" in st.session_state:
+ return st.session_state.user_profile.get("font_size", "Medium")
+ return "Medium"
+
+
+def apply_global_font_size():
+ """
+ Apply the user's font size preference globally across the application.
+ Call this function in your main app to apply font size changes.
+ """
+ font_size = get_user_font_size()
+
+ # Font size mappings
+ font_sizes = {
+ "Small": "14px",
+ "Medium": "16px",
+ "Large": "18px"
+ }
+
+ selected_size = font_sizes.get(font_size, "16px")
+
+ # Apply CSS to change font size globally
+ st.markdown(f"""
+
+ """, unsafe_allow_html=True)
+
+
+def get_user_profile_picture():
+ """Get the current user's profile picture"""
+ if "user_profile" in st.session_state:
+ return st.session_state.user_profile.get("profile_picture", None)
+ return None
\ No newline at end of file
diff --git a/components/sidebar.py b/components/sidebar.py
index 208d066..462617e 100644
--- a/components/sidebar.py
+++ b/components/sidebar.py
@@ -3,6 +3,9 @@
from datetime import datetime
from core.utils import create_new_conversation, get_current_time
from core.theme import get_current_theme, toggle_theme, set_palette, PALETTES
+from components.profile import initialize_profile_state, render_profile_section
+from streamlit_js_eval import streamlit_js_eval
+import requests
# --- Structured Emergency Resources ---
GLOBAL_RESOURCES = [
@@ -18,6 +21,69 @@
"url": "https://www.childhelplineinternational.org/"}
]
+
+def get_country_from_coords(lat, lon):
+ try:
+ url = f"https://geocode.maps.co/reverse?lat={lat}&lon={lon}"
+ resp = requests.get(url, timeout=5)
+ if resp.status_code == 200:
+ data = resp.json()
+ return data.get("address", {}).get("country_code", "").upper()
+ except:
+ pass
+ return None
+
+def get_user_country():
+ # 1. Try to get user's actual browser location (via JS)
+ coords = streamlit_js_eval(
+ js_expressions="""
+ new Promise((resolve, reject) => {
+ navigator.geolocation.getCurrentPosition(
+ position => resolve({
+ latitude: position.coords.latitude,
+ longitude: position.coords.longitude
+ }),
+ error => resolve(null)
+ );
+ });
+ """,
+ key="get_coords"
+ )
+
+ if coords and "latitude" in coords and "longitude" in coords:
+ country = get_country_from_coords(coords["latitude"], coords["longitude"])
+ if country:
+ return country
+
+ # 2. Fallback to IP-based location using ipapi.co (no key required)
+ try:
+ resp = requests.get("https://ipapi.co/json/", timeout=3)
+ if resp.status_code == 200:
+ return resp.json().get("country_code", "").upper()
+ except:
+ pass
+
+ return None # final fallback if everything fails
+
+country_helplines = {
+ "US": [
+ "National Suicide Prevention Lifeline: 988",
+ "Crisis Text Line: Text HOME to 741741",
+ "SAMHSA National Helpline: 1-800-662-4357"
+ ],
+ "IN": [
+ "AASRA: 9152987821",
+ "Sneha Foundation: 044-24640050"
+ ],
+ "GB": [
+ "Samaritans: 116 123"
+ ],
+ "AU": [
+ "Lifeline: 13 11 14"
+ ]
+}
+IASP_LINK = "https://findahelpline.com/"
+
mental_health_resources_full = {
"Depression & Mood Disorders": {
"description": "Information on understanding and coping with depression, persistent depressive disorder, and other mood-related challenges.",
@@ -74,9 +140,17 @@
def render_sidebar():
"""Renders the left and right sidebars."""
-
+
with st.sidebar:
+ render_profile_section()
+
+ st.markdown("### ๐ Explore")
+ st.page_link("pages/Journaling.py", label="๐ Journaling", use_container_width=True)
+ st.page_link("pages/Yoga.py", label="๐ง Yoga", use_container_width=True)
+ st.markdown("---")
+
st.markdown("### ๐ฌ Conversations")
+
if "show_quick_start_prompts" not in st.session_state:
st.session_state.show_quick_start_prompts = False
if "pre_filled_chat_input" not in st.session_state:
@@ -88,6 +162,7 @@ def render_sidebar():
create_new_conversation()
st.session_state.show_quick_start_prompts = True
st.rerun()
+
if st.session_state.show_quick_start_prompts:
st.markdown("---")
st.markdown("**Start with a common topic:**")
@@ -108,6 +183,7 @@ def render_sidebar():
st.markdown("---")
+
if st.session_state.conversations:
if "delete_candidate" not in st.session_state:
for i, convo in enumerate(st.session_state.conversations):
@@ -125,9 +201,19 @@ def render_sidebar():
st.session_state.active_conversation = i
st.rerun()
with col2:
- if st.button("๐๏ธ", key=f"delete_{i}", type="primary"):
- st.session_state.delete_candidate = i
- st.rerun()
+ if convo["messages"]:
+ if st.button("๐๏ธ", key=f"delete_{i}", type="primary", use_container_width=True):
+ st.session_state.delete_candidate = i
+ st.rerun()
+ else:
+ st.button(
+ "๐๏ธ",
+ key=f"delete_{i}",
+ type="primary",
+ use_container_width=True,
+ disabled=not convo["messages"] # Disable if it's a new/empty conversation
+ )
+
else:
st.warning(
@@ -298,6 +384,21 @@ def render_sidebar():
for resource in GLOBAL_RESOURCES:
st.markdown(
f"**{resource['name']}**: {resource['desc']} [Visit Website]({resource['url']})")
+
+ # Provide localized helplines based on user's country
+ user_country = get_user_country()
+ country_label = user_country if user_country else "your country"
+ st.markdown("### ๐จ Emergency Help")
+ if user_country and user_country in country_helplines:
+ st.markdown(f"**Helplines for {country_label}:**")
+ for line in country_helplines[user_country]:
+ st.markdown(f"โข {line}")
+ else:
+ st.markdown(
+ f"Couldn't detect a local helpline for {country_label}. [Find help worldwide via IASP]({IASP_LINK})"
+ )
+
+ st.markdown("---")
# Theme toggle in sidebar
with st.expander("๐จ Theme Settings"):
@@ -415,4 +516,4 @@ def render_sidebar():
*"It's absolutely okay not to be okay :)"*
๐ Enhanced Version - May 2025
- """)
\ No newline at end of file
+ """)
diff --git a/core/config.py b/core/config.py
index a7fa361..e385eb8 100644
--- a/core/config.py
+++ b/core/config.py
@@ -14,7 +14,7 @@
"menu_items": None
}
-st.set_page_config(**PAGE_CONFIG)
+#st.set_page_config(**PAGE_CONFIG)
# ---------- Custom Dropdown Style ----------
st.markdown("""
diff --git a/css/styles.py b/css/styles.py
index b4ee017..18ec9cd 100644
--- a/css/styles.py
+++ b/css/styles.py
@@ -63,23 +63,10 @@ def apply_custom_css():
--transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}}
- /* Streamlit header and toolbar fixes */
.stApp > header {{
background-color: transparent !important;
}}
- div[data-testid="stToolbar"] {{
- visibility: hidden;
- }}
-
- .stDeployButton {{
- visibility: hidden;
- }}
-
- footer {{
- visibility: hidden;
- }}
-
/* Main app background and styling */
.stApp {{
background-image: url("data:image/jpeg;base64,{base64_image}");
@@ -498,7 +485,7 @@ def apply_custom_css():
.stTextInput > div > div > input,
.stTextArea > div > div > textarea {{
background: var(--glass-effect) !important;
- background-color: #FFFFD0 !important;
+ background-color: #FFDDEE !important;
border: 2px solid var(--border) !important;
border-radius: var(--radius) !important;
font-size: 1em !important;
diff --git a/data/Yoga.json b/data/Yoga.json
new file mode 100644
index 0000000..765d13f
--- /dev/null
+++ b/data/Yoga.json
@@ -0,0 +1,91 @@
+{
+ "Anxious": {
+ "sanskrit_name": "Viparita Karani",
+ "english_name": "Legs-Up-the-Wall Pose",
+ "benefit": "Relieves anxiety and calms the nervous system.",
+ "steps": [
+ "Lie on your back and extend your legs up against a wall.",
+ "Keep your arms relaxed at your sides.",
+ "Breathe deeply and relax for 5-15 minutes."
+ ]
+ },
+ "Sad": {
+ "sanskrit_name": "Balasana",
+ "english_name": "Child's Pose",
+ "benefit": "Soothes sadness and encourages inner reflection.",
+ "steps": [
+ "Kneel down and sit back on your heels.",
+ "Bend forward and stretch your arms in front of you.",
+ "Rest your forehead on the mat and take deep breaths."
+ ]
+ },
+ "Stressed": {
+ "sanskrit_name": "Bhramari Pranayama",
+ "english_name": "Bee Breath",
+ "benefit": "Reduces stress and relaxes the mind through calming vibrations.",
+ "steps": [
+ "Sit comfortably with your spine straight.",
+ "Close your eyes and take a deep breath in.",
+ "Exhale slowly while making a humming sound like a bee.",
+ "Repeat this for 5-10 rounds."
+ ]
+ },
+ "Motivated": {
+ "sanskrit_name": "Surya Namaskar",
+ "english_name": "Sun Salutation",
+ "benefit": "Boosts energy, motivation, and positivity through a full-body flow.",
+ "steps": [
+ "Stand tall in Mountain Pose with hands in prayer.",
+ "Inhale, raise your arms overhead and arch back slightly.",
+ "Exhale, fold forward and place your hands on the ground.",
+ "Inhale, lift your head and chest halfway up.",
+ "Exhale, step back into a plank and lower to the floor.",
+ "Inhale into Cobra pose.",
+ "Exhale into Downward Dog and hold.",
+ "Inhale, step forward, and repeat the flow for 3-5 rounds."
+ ]
+ },
+ "Tired": {
+ "sanskrit_name": "Savasana",
+ "english_name": "Corpse Pose",
+ "benefit": "Promotes deep rest and rejuvenation.",
+ "steps": [
+ "Lie flat on your back with legs extended and arms by your sides.",
+ "Close your eyes and let your entire body relax.",
+ "Focus on your breath and stay in this pose for 5-10 minutes."
+ ]
+ },
+ "Angry": {
+ "sanskrit_name": "Simhasana",
+ "english_name": "Lion's Breath",
+ "benefit": "Releases anger and tension through expressive breath.",
+ "steps": [
+ "Sit in a comfortable kneeling position.",
+ "Inhale deeply through the nose.",
+ "Exhale forcefully through the mouth while sticking out your tongue and roaring like a lion.",
+ "Repeat for 5 rounds."
+ ]
+ },
+ "Lonely": {
+ "sanskrit_name": "Matsyasana",
+ "english_name": "Fish Pose",
+ "benefit": "Opens the heart center and promotes emotional release.",
+ "steps": [
+ "Lie on your back and slide your hands under your hips.",
+ "Press your forearms and elbows into the ground.",
+ "Lift your chest and gently drop your head back.",
+ "Hold for 5 breaths, then relax."
+ ]
+ },
+ "Joyful": {
+ "sanskrit_name": "Natarajasana",
+ "english_name": "Dancer's Pose",
+ "benefit": "Celebrates balance, grace, and self-expression.",
+ "steps": [
+ "Stand tall and shift your weight to your left foot.",
+ "Bend your right knee and hold your ankle from behind.",
+ "Extend your left arm forward and lift your right leg up.",
+ "Hold for 5-7 breaths, then switch sides."
+ ]
+ }
+}
diff --git a/journals.db b/journals.db
new file mode 100644
index 0000000..597e43a
Binary files /dev/null and b/journals.db differ
diff --git a/pages/Journaling.py b/pages/Journaling.py
new file mode 100644
index 0000000..715e19c
--- /dev/null
+++ b/pages/Journaling.py
@@ -0,0 +1,173 @@
+import streamlit as st
+import sqlite3
+import datetime
+import base64
+from uuid import uuid4
+
+def get_base64_of_bin_file(bin_file_path):
+ with open(bin_file_path, 'rb') as f:
+ data = f.read()
+ return base64.b64encode(data).decode()
+
+def set_background(main_bg_path, sidebar_bg_path=None):
+ main_bg = get_base64_of_bin_file(main_bg_path)
+ sidebar_bg = get_base64_of_bin_file(sidebar_bg_path) if sidebar_bg_path else main_bg
+
+ st.markdown(
+ f"""
+
+ """,
+ unsafe_allow_html=True
+ )
+
+def analyze_sentiment(entry: str) -> str:
+ if any(word in entry.lower() for word in ['sad', 'tired', 'upset', 'angry']):
+ return "Negative"
+ elif any(word in entry.lower() for word in ['happy', 'grateful', 'joy']):
+ return "Positive"
+ return "Neutral"
+
+DB_PATH = "journals.db"
+
+def init_journal_db():
+ conn = sqlite3.connect(DB_PATH)
+ cursor = conn.cursor()
+ cursor.execute("""
+ CREATE TABLE IF NOT EXISTS journal_entries (
+ id TEXT PRIMARY KEY,
+ email TEXT,
+ entry TEXT,
+ sentiment TEXT,
+ date TEXT
+ )
+ """)
+ conn.commit()
+ conn.close()
+
+def save_entry(email, entry, sentiment):
+ conn = sqlite3.connect(DB_PATH)
+ cursor = conn.cursor()
+ cursor.execute("""
+ INSERT INTO journal_entries (id, email, entry, sentiment, date)
+ VALUES (?, ?, ?, ?, ?)
+ """, (str(uuid4()), email, entry, sentiment, str(datetime.date.today())))
+ conn.commit()
+ conn.close()
+
+def fetch_entries(email, sentiment_filter=None, start_date=None, end_date=None):
+ conn = sqlite3.connect(DB_PATH)
+ cursor = conn.cursor()
+
+ query = """
+ SELECT entry, sentiment, date FROM journal_entries
+ WHERE email = ?
+ """
+ params = [email]
+
+ if sentiment_filter and sentiment_filter != "All":
+ query += " AND sentiment = ?"
+ params.append(sentiment_filter)
+
+ if start_date and end_date:
+ query += " AND date BETWEEN ? AND ?"
+ params.extend([start_date, end_date])
+
+ rows = cursor.execute(query, params).fetchall()
+ conn.close()
+ return rows
+
+def journaling_app():
+ set_background("mint.png") # Use your background image path or comment this line
+ st.markdown(
+ """
+
+ """,
+ unsafe_allow_html=True
+ )
+ email = st.session_state.get("user_email")
+ if not email:
+ st.warning("โ ๏ธ Please login from the main page to access your journal.")
+ st.stop()
+
+ st.title("๐ My Journal")
+ st.markdown("Write about your day, thoughts, or anything you'd like to reflect on.")
+
+ with st.form("journal_form"):
+ journal_text = st.text_area("How are you feeling today?", height=200)
+ submitted = st.form_submit_button("Submit Entry")
+
+ if submitted and journal_text.strip():
+ sentiment = analyze_sentiment(journal_text)
+ save_entry(email, journal_text, sentiment)
+ st.success(f"Entry saved! Sentiment: **{sentiment}**")
+
+ st.markdown("---")
+ st.subheader("๐ Your Journal Entries")
+
+ filter_sentiment = st.selectbox("Filter by Sentiment", ["All", "Positive", "Neutral", "Negative"])
+
+ col1, col2 = st.columns(2)
+ with col1:
+ start_date = st.date_input("Start Date", value=datetime.date.today().replace(day=1))
+ with col2:
+ end_date = st.date_input("End Date", value=datetime.date.today())
+
+ entries = fetch_entries(email, sentiment_filter=filter_sentiment, start_date=start_date, end_date=end_date)
+
+ if not entries:
+ st.info("No entries found for selected filters.")
+ else:
+ for entry, sentiment, date in entries:
+ with st.expander(f"{date} - Mood: {sentiment}"):
+ st.write(entry)
+
+init_journal_db()
+journaling_app()
diff --git a/pages/Yoga.py b/pages/Yoga.py
new file mode 100644
index 0000000..270aca2
--- /dev/null
+++ b/pages/Yoga.py
@@ -0,0 +1,298 @@
+import streamlit as st
+import json
+import os
+import base64
+from streamlit_lottie import st_lottie
+
+st.set_page_config(page_title="๐ง Yoga for Mental Health", layout="centered")
+
+def load_lottiefile(filepath: str):
+ try:
+ with open(filepath, "r") as f:
+ return json.load(f)
+ except FileNotFoundError:
+ return None
+
+# Function to encode image to base64
+def get_base64_of_bin_file(bin_file):
+ try:
+ with open(bin_file, 'rb') as f:
+ data = f.read()
+ return base64.b64encode(data).decode()
+ except FileNotFoundError:
+ st.error(f"Background image not found at {bin_file}. Please check the path.")
+ return ""
+
+lottie_yoga = load_lottiefile("assets/yoga_animation.json")
+
+# --- Load Yoga Data ---
+try:
+ with open(os.path.join("data", "Yoga.json"), "r") as f:
+ yoga_data = json.load(f)
+except FileNotFoundError:
+ yoga_data = {}
+
+background_image_path = "lavender.png"
+base64_background_image = get_base64_of_bin_file(background_image_path)
+
+# --- Custom CSS ---
+st.markdown(f"""
+
+""", unsafe_allow_html=True)
+
+# --- Animation ---
+st.markdown('