diff --git a/TalkHeal.py b/TalkHeal.py index 8045f44..dce62a9 100644 --- a/TalkHeal.py +++ b/TalkHeal.py @@ -1,7 +1,9 @@ + import streamlit as st from auth.auth_utils import init_db from components.login_page import show_login_page from core.utils import save_conversations, load_conversations +from components.onboarding import onboarding_flow # HANDLES ALL SESSION STATE VALUES @@ -38,11 +40,17 @@ def init_session_state(): 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() +# --- ONBOARDING FOR FIRST-TIME USERS --- +if not st.session_state.get("has_onboarded", False): + onboarding_flow() + st.stop() + # --- TOP RIGHT BUTTONS: THEME TOGGLE & LOGOUT --- if st.session_state.get("authenticated", False): col_spacer, col_theme, col_emergency, col_about, col_logout = st.columns([0.7, 0.1, 0.35, 0.2, 0.2]) diff --git a/components/onboarding.py b/components/onboarding.py new file mode 100644 index 0000000..fb08b6e --- /dev/null +++ b/components/onboarding.py @@ -0,0 +1,101 @@ +""" +Onboarding component for first-time users. +Guides through profile setup, AI tone selection, and a quick app tour. +""" + +import streamlit as st +from components.profile import initialize_profile_state, render_profile_settings +from core.peer_matching import MENTAL_HEALTH_CONCERNS, INTERESTS, save_user_profile + +AI_TONES = [ + "Empathetic", + "Motivational", + "Calm", + "Friendly", + "Professional" +] + +FEATURE_TOUR = [ + {"title": "Mood Tracker", "desc": "Log and visualize your mood over time."}, + {"title": "Self-Help Tools", "desc": "Access exercises and resources for well-being."}, + {"title": "Focus Sessions", "desc": "Guided sessions to help you concentrate and relax."}, + {"title": "Profile & Personalization", "desc": "Customize your experience and preferences."}, + {"title": "Peer Groups", "desc": "Connect with others who share similar interests and experiences."} +] + +def onboarding_flow(): + initialize_profile_state() + if "onboarding_step" not in st.session_state: + st.session_state.onboarding_step = 0 + if "user_profile" not in st.session_state: + st.session_state.user_profile = {} + + step = st.session_state.onboarding_step + + if step == 0: + st.title("šŸ‘‹ Welcome to TalkHeal!") + st.write("Let's set up your profile to personalize your experience.") + render_profile_settings() + if st.button("Next: Choose AI Tone", type="primary"): + st.session_state.onboarding_step = 1 + st.rerun() + elif step == 1: + st.title("šŸ¤– Choose Your AI Tone") + st.write("How would you like the AI to interact with you?") + tone = st.radio("Select a tone:", AI_TONES, key="ai_tone_radio") + if st.button("Next: Interests & Concerns", type="primary"): + st.session_state.user_profile["ai_tone"] = tone + st.session_state.onboarding_step = 2 + st.rerun() + elif step == 2: + st.title("🧠 Your Interests & Concerns") + st.write("This helps us connect you with like-minded peers.") + + col1, col2 = st.columns(2) + with col1: + st.subheader("Mental Health Areas") + selected_concerns = st.multiselect( + "Select areas you'd like support with:", + options=MENTAL_HEALTH_CONCERNS, + default=[], + help="Choose topics you're interested in discussing" + ) + + with col2: + st.subheader("Personal Interests") + selected_interests = st.multiselect( + "Select your interests:", + options=INTERESTS, + default=[], + help="Activities and topics you enjoy" + ) + + if st.button("Next: App Tour", type="primary"): + # Save to user profile + if selected_concerns or selected_interests: + st.session_state.user_profile["concerns"] = selected_concerns + st.session_state.user_profile["interests"] = selected_interests + + # If authenticated, save to database + if st.session_state.get("authenticated", False): + save_user_profile( + email=st.session_state.user_email, + name=st.session_state.user_name, + concerns=selected_concerns, + interests=selected_interests + ) + + st.session_state.onboarding_step = 3 + st.rerun() + elif step == 3: + st.title("šŸš€ Quick App Tour") + st.write("Here's what you can do in TalkHeal:") + for feature in FEATURE_TOUR: + st.markdown(f"**{feature['title']}**: {feature['desc']}") + if st.button("Finish Onboarding", type="primary"): + st.session_state.onboarding_step = 4 + st.session_state.has_onboarded = True + st.success("You're all set! Enjoy using TalkHeal.") + st.rerun() + else: + st.success("Onboarding complete! You can update your profile or preferences anytime from the sidebar.") diff --git a/components/profile.py b/components/profile.py index 90d8943..7c80322 100644 --- a/components/profile.py +++ b/components/profile.py @@ -25,7 +25,8 @@ def initialize_profile_state(): "name": "", "profile_picture": None, "join_date": datetime.now().strftime("%B %Y"), - "font_size": "Medium" + "font_size": "Medium", + "ai_tone": "Empathetic" } diff --git a/components/sidebar.py b/components/sidebar.py index cc6af84..01c9ea8 100644 --- a/components/sidebar.py +++ b/components/sidebar.py @@ -273,7 +273,11 @@ def render_sidebar(): if st.button("šŸ“Œ View Pinned Messages", use_container_width=True): st.session_state.active_page = "PinnedMessages" st.rerun() - + + # Peer Groups navigation + if st.button("šŸ‘„ Find Peer Groups", use_container_width=True): + st.switch_page("pages/PeerGroups.py") + with st.sidebar: # Theme Settings Section diff --git a/core/peer_matching.py b/core/peer_matching.py new file mode 100644 index 0000000..5fe654b --- /dev/null +++ b/core/peer_matching.py @@ -0,0 +1,421 @@ +""" +Peer Matching System for TalkHeal + +This module implements a machine learning-based approach to match users +with small groups of peers based on their interests and mental health concerns. +It uses vector embeddings and clustering to form meaningful peer groups. +""" + +import streamlit as st +import numpy as np +import pandas as pd +from sklearn.metrics.pairwise import cosine_similarity +from sklearn.cluster import KMeans +from sklearn.decomposition import PCA +import sqlite3 +import json +import plotly.express as px +from datetime import datetime + +# Constants +MENTAL_HEALTH_CONCERNS = [ + "Depression & Mood Disorders", + "Anxiety & Panic Disorders", + "Bipolar Disorder", + "PTSD & Trauma", + "OCD & Related Disorders", + "Eating Disorders", + "Substance Use Disorders", + "ADHD & Neurodevelopmental", + "Personality Disorders", + "Sleep Disorders", + "Stress Management", + "Grief & Loss", + "Self-Esteem Issues", + "Relationship Challenges", + "Work-Life Balance" +] + +INTERESTS = [ + "Meditation & Mindfulness", + "Physical Exercise", + "Creative Arts & Expression", + "Nature & Outdoors", + "Reading & Learning", + "Music & Sound Therapy", + "Social Connection", + "Volunteering & Helping Others", + "Spiritual Practices", + "Journaling & Self-Reflection", + "Technology & Gaming", + "Cooking & Nutrition", + "Travel & New Experiences", + "Professional Development", + "Family & Parenting" +] + +# Database functions +def init_peer_matching_db(): + """Initialize the database for peer matching""" + conn = sqlite3.connect("users.db") + cursor = conn.cursor() + + # Create user profiles table if not exists + cursor.execute(""" + CREATE TABLE IF NOT EXISTS user_profiles ( + email TEXT PRIMARY KEY, + name TEXT NOT NULL, + concerns TEXT, + interests TEXT, + profile_vector TEXT, + updated_at TEXT NOT NULL + ) + """) + + # Create peer groups table if not exists + cursor.execute(""" + CREATE TABLE IF NOT EXISTS peer_groups ( + group_id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT, + created_at TEXT NOT NULL + ) + """) + + # Create group membership table if not exists + cursor.execute(""" + CREATE TABLE IF NOT EXISTS group_membership ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT, + group_id INTEGER, + joined_at TEXT NOT NULL, + FOREIGN KEY (email) REFERENCES user_profiles (email), + FOREIGN KEY (group_id) REFERENCES peer_groups (group_id) + ) + """) + + conn.commit() + conn.close() + +def save_user_profile(email, name, concerns, interests): + """Save user profile with concerns and interests""" + conn = sqlite3.connect("users.db") + cursor = conn.cursor() + + # Convert lists to JSON strings + concerns_json = json.dumps(concerns) + interests_json = json.dumps(interests) + + # Create profile vector + profile_vector = create_profile_vector(concerns, interests) + profile_vector_json = json.dumps(profile_vector.tolist()) + + current_time = datetime.now().isoformat() + + # Check if user profile exists + cursor.execute("SELECT * FROM user_profiles WHERE email = ?", (email,)) + if cursor.fetchone(): + # Update existing profile + cursor.execute(""" + UPDATE user_profiles + SET name = ?, concerns = ?, interests = ?, profile_vector = ?, updated_at = ? + WHERE email = ? + """, (name, concerns_json, interests_json, profile_vector_json, current_time, email)) + else: + # Insert new profile + cursor.execute(""" + INSERT INTO user_profiles (email, name, concerns, interests, profile_vector, updated_at) + VALUES (?, ?, ?, ?, ?, ?) + """, (email, name, concerns_json, interests_json, profile_vector_json, current_time)) + + conn.commit() + conn.close() + + return True + +def get_user_profile(email): + """Get user profile data""" + conn = sqlite3.connect("users.db") + cursor = conn.cursor() + + cursor.execute("SELECT name, concerns, interests, profile_vector FROM user_profiles WHERE email = ?", (email,)) + result = cursor.fetchone() + conn.close() + + if result: + return { + "name": result[0], + "concerns": json.loads(result[1]) if result[1] else [], + "interests": json.loads(result[2]) if result[2] else [], + "profile_vector": np.array(json.loads(result[3])) if result[3] else None + } + else: + return None + +def get_all_user_profiles(): + """Get all user profiles""" + conn = sqlite3.connect("users.db") + cursor = conn.cursor() + + cursor.execute("SELECT email, name, concerns, interests, profile_vector FROM user_profiles") + results = cursor.fetchall() + conn.close() + + profiles = [] + for result in results: + profiles.append({ + "email": result[0], + "name": result[1], + "concerns": json.loads(result[2]) if result[2] else [], + "interests": json.loads(result[3]) if result[3] else [], + "profile_vector": np.array(json.loads(result[4])) if result[4] else None + }) + + return profiles + +def create_peer_group(name, description, member_emails): + """Create a new peer group with members""" + conn = sqlite3.connect("users.db") + cursor = conn.cursor() + + current_time = datetime.now().isoformat() + + # Create the group + cursor.execute(""" + INSERT INTO peer_groups (name, description, created_at) + VALUES (?, ?, ?) + """, (name, description, current_time)) + + # Get the new group ID + group_id = cursor.lastrowid + + # Add members to the group + for email in member_emails: + cursor.execute(""" + INSERT INTO group_membership (email, group_id, joined_at) + VALUES (?, ?, ?) + """, (email, group_id, current_time)) + + conn.commit() + conn.close() + + return group_id + +def get_user_groups(email): + """Get all groups a user is a member of""" + conn = sqlite3.connect("users.db") + cursor = conn.cursor() + + cursor.execute(""" + SELECT pg.group_id, pg.name, pg.description, pg.created_at + FROM peer_groups pg + JOIN group_membership gm ON pg.group_id = gm.group_id + WHERE gm.email = ? + """, (email,)) + + groups = cursor.fetchall() + conn.close() + + return [{"id": g[0], "name": g[1], "description": g[2], "created_at": g[3]} for g in groups] + +def get_group_members(group_id): + """Get all members of a group""" + conn = sqlite3.connect("users.db") + cursor = conn.cursor() + + cursor.execute(""" + SELECT up.email, up.name, gm.joined_at + FROM user_profiles up + JOIN group_membership gm ON up.email = gm.email + WHERE gm.group_id = ? + """, (group_id,)) + + members = cursor.fetchall() + conn.close() + + return [{"email": m[0], "name": m[1], "joined_at": m[2]} for m in members] + +# ML functions +def create_profile_vector(concerns, interests): + """Create a numerical vector representation of user profile""" + # Initialize zero vectors for concerns and interests + concerns_vector = np.zeros(len(MENTAL_HEALTH_CONCERNS)) + interests_vector = np.zeros(len(INTERESTS)) + + # Set 1 for each selected concern and interest + for concern in concerns: + if concern in MENTAL_HEALTH_CONCERNS: + idx = MENTAL_HEALTH_CONCERNS.index(concern) + concerns_vector[idx] = 1 + + for interest in interests: + if interest in INTERESTS: + idx = INTERESTS.index(interest) + interests_vector[idx] = 1 + + # Concatenate vectors with equal weighting + return np.concatenate([concerns_vector, interests_vector]) + +def find_similar_users(user_email, min_similarity=0.3, max_results=5): + """Find users similar to the given user""" + # Get all user profiles + all_profiles = get_all_user_profiles() + + # Get target user profile + user_profile = next((p for p in all_profiles if p["email"] == user_email), None) + if not user_profile or user_profile["profile_vector"] is None: + return [] + + # Calculate similarity scores + similarity_scores = [] + for profile in all_profiles: + if profile["email"] != user_email and profile["profile_vector"] is not None: + sim_score = cosine_similarity( + user_profile["profile_vector"].reshape(1, -1), + profile["profile_vector"].reshape(1, -1) + )[0][0] + + if sim_score >= min_similarity: + similarity_scores.append({ + "email": profile["email"], + "name": profile["name"], + "similarity": sim_score, + "shared_concerns": [c for c in profile["concerns"] if c in user_profile["concerns"]], + "shared_interests": [i for i in profile["interests"] if i in user_profile["interests"]] + }) + + # Sort by similarity score and return top N + similarity_scores.sort(key=lambda x: x["similarity"], reverse=True) + return similarity_scores[:max_results] + +def create_peer_groups_from_all_users(n_groups=None, min_group_size=3, max_group_size=7): + """Create peer groups from all users using K-means clustering""" + # Get all user profiles + all_profiles = get_all_user_profiles() + + # Extract valid profile vectors + valid_profiles = [p for p in all_profiles if p["profile_vector"] is not None] + + if len(valid_profiles) < min_group_size: + return {"success": False, "message": f"Need at least {min_group_size} users with complete profiles"} + + # Create feature matrix + feature_matrix = np.vstack([p["profile_vector"] for p in valid_profiles]) + + # Determine optimal number of clusters if not specified + if n_groups is None: + # Simple heuristic: roughly 1 group per 5 users, with bounds + n_groups = max(2, min(10, len(valid_profiles) // 5)) + + # Apply K-means clustering + kmeans = KMeans(n_clusters=n_groups, random_state=42) + cluster_labels = kmeans.fit_predict(feature_matrix) + + # Create groups based on clusters + groups = {} + for i, label in enumerate(cluster_labels): + if label not in groups: + groups[label] = [] + groups[label].append(valid_profiles[i]) + + # Generate group names and descriptions + created_groups = [] + for label, members in groups.items(): + # Skip groups that are too small + if len(members) < min_group_size: + continue + + # Limit group size + if len(members) > max_group_size: + members = members[:max_group_size] + + # Find common concerns and interests + common_concerns = set(members[0]["concerns"]) + common_interests = set(members[0]["interests"]) + + for member in members[1:]: + common_concerns &= set(member["concerns"]) + common_interests &= set(member["interests"]) + + # Generate group name and description + group_name = "Peer Support Group" + if common_concerns: + group_name = f"{list(common_concerns)[0]} Support Group" + elif common_interests: + group_name = f"{list(common_interests)[0]} Interest Group" + + description = "A peer support group for shared experiences and mutual growth." + if common_concerns: + concerns_str = ", ".join(list(common_concerns)[:3]) + description = f"Support group focused on {concerns_str}" + if common_interests: + interests_str = ", ".join(list(common_interests)[:2]) + description += f" with shared interests in {interests_str}" + elif common_interests: + interests_str = ", ".join(list(common_interests)[:3]) + description = f"Group connecting through shared interests in {interests_str}" + + # Create the group in the database + member_emails = [member["email"] for member in members] + group_id = create_peer_group(group_name, description, member_emails) + + created_groups.append({ + "id": group_id, + "name": group_name, + "description": description, + "member_count": len(members) + }) + + return {"success": True, "groups": created_groups} + +def visualize_user_clusters(current_user_email=None): + """Generate a visualization of user clusters""" + # Get all user profiles + all_profiles = get_all_user_profiles() + + # Extract valid profile vectors + valid_profiles = [p for p in all_profiles if p["profile_vector"] is not None] + + if len(valid_profiles) < 3: + return None + + # Create feature matrix + feature_matrix = np.vstack([p["profile_vector"] for p in valid_profiles]) + + # Reduce dimensionality to 2D for visualization + pca = PCA(n_components=2) + reduced_features = pca.fit_transform(feature_matrix) + + # Create DataFrame for plotting + df = pd.DataFrame({ + 'x': reduced_features[:, 0], + 'y': reduced_features[:, 1], + 'name': [p["name"] for p in valid_profiles], + 'email': [p["email"] for p in valid_profiles], + }) + + # Highlight current user if specified + if current_user_email: + df['is_current_user'] = df['email'] == current_user_email + else: + df['is_current_user'] = False + + # Create interactive scatter plot + fig = px.scatter( + df, x='x', y='y', + hover_name='name', + color='is_current_user', + color_discrete_map={True: '#FF5757', False: '#3A86FF'}, + title='User Similarity Map', + labels={'x': 'Dimension 1', 'y': 'Dimension 2'}, + size_max=10, + ) + + fig.update_layout( + plot_bgcolor='rgba(240, 240, 240, 0.8)', + paper_bgcolor='rgba(0, 0, 0, 0)', + font_color='#333333', + height=400, + ) + + return fig \ No newline at end of file diff --git a/pages/PeerGroups.py b/pages/PeerGroups.py new file mode 100644 index 0000000..0f0408e --- /dev/null +++ b/pages/PeerGroups.py @@ -0,0 +1,225 @@ +""" +Peer Groups Page - TalkHeal + +This page allows users to find and connect with peers who have similar +interests and mental health concerns using a machine learning-based approach. +""" + +import streamlit as st +import time +from core.peer_matching import ( + init_peer_matching_db, + save_user_profile, + get_user_profile, + get_user_groups, + get_group_members, + find_similar_users, + create_peer_groups_from_all_users, + visualize_user_clusters, + MENTAL_HEALTH_CONCERNS, + INTERESTS +) + +def render_profile_form(user_email, user_name): + """Render the profile form for interests and concerns""" + st.markdown("### šŸ“‹ Your Matching Profile") + + # Get existing profile if available + user_profile = get_user_profile(user_email) + + # Default values + default_concerns = user_profile["concerns"] if user_profile else [] + default_interests = user_profile["interests"] if user_profile else [] + + with st.form("profile_form"): + st.markdown("#### Mental Health Areas") + st.markdown("Select areas you'd like to connect with others about:") + selected_concerns = st.multiselect( + "Select mental health topics you're interested in discussing", + options=MENTAL_HEALTH_CONCERNS, + default=default_concerns, + help="Choose topics you want to find support with in peer groups" + ) + + st.markdown("#### Personal Interests") + st.markdown("Select interests and activities you enjoy:") + selected_interests = st.multiselect( + "Select your interests and wellness activities", + options=INTERESTS, + default=default_interests, + help="Your interests help find peers with similar approaches to wellness" + ) + + # Form submission + submitted = st.form_submit_button("Save Profile", type="primary") + + # Process form submission + if submitted: + if not selected_concerns: + st.warning("Please select at least one mental health area") + return False + + if not selected_interests: + st.warning("Please select at least one interest") + return False + + # Save profile + success = save_user_profile( + email=user_email, + name=user_name, + concerns=selected_concerns, + interests=selected_interests + ) + + if success: + st.success("Profile saved successfully! We'll use this to find peers with similar experiences.") + return True + else: + st.error("Error saving profile. Please try again.") + return False + + return False + +def render_peer_groups(user_email): + """Render the user's peer groups""" + user_groups = get_user_groups(user_email) + + if user_groups: + st.markdown("### šŸ‘„ Your Peer Groups") + + for group in user_groups: + with st.expander(f"{group['name']} ({len(get_group_members(group['id']))} members)"): + st.markdown(f"**Description:** {group['description']}") + st.markdown(f"**Created:** {group['created_at'][:10]}") + + # Show members + members = get_group_members(group['id']) + st.markdown("#### Members:") + for member in members: + st.markdown(f"- **{member['name']}** (joined {member['joined_at'][:10]})") + + # Group chat placeholder + st.markdown("#### Group Chat") + st.info("Group chat functionality coming soon!") + + # Message input + message = st.text_input("Type a message to the group", key=f"msg_{group['id']}") + if st.button("Send", key=f"send_{group['id']}"): + st.info("Message sending will be implemented soon!") + else: + st.info("You're not a member of any peer groups yet. We'll help you find the right match!") + +def render_similar_users(user_email): + """Render similar users""" + similar_users = find_similar_users(user_email) + + if similar_users: + st.markdown("### šŸ¤ People With Similar Interests") + + # Display visualization + fig = visualize_user_clusters(user_email) + if fig: + st.plotly_chart(fig, use_container_width=True) + + # List similar users + for i, user in enumerate(similar_users): + similarity_percentage = int(user["similarity"] * 100) + + with st.container(): + col1, col2 = st.columns([1, 3]) + + with col1: + st.markdown(f"### {similarity_percentage}%") + st.caption("Match") + + with col2: + st.markdown(f"#### {user['name']}") + + # Show shared concerns and interests + if user["shared_concerns"]: + concerns_str = ", ".join(user["shared_concerns"][:3]) + st.markdown(f"**Similar concerns:** {concerns_str}") + + if user["shared_interests"]: + interests_str = ", ".join(user["shared_interests"][:3]) + st.markdown(f"**Similar interests:** {interests_str}") + + if st.button("Connect", key=f"connect_{i}"): + st.info("Direct connections will be implemented soon!") + + st.markdown("---") + else: + st.info("Complete your profile to find similar users!") + +def render_group_formation(): + """Admin section for group formation""" + st.markdown("### šŸ› ļø Admin: Group Formation") + + # Options for group formation + n_groups = st.slider("Number of groups to form", 2, 10, 3) + min_group_size = st.slider("Minimum members per group", 2, 5, 3) + max_group_size = st.slider("Maximum members per group", 5, 15, 7) + + if st.button("Form Peer Groups", type="primary"): + with st.spinner("Forming peer groups based on similarity..."): + # Simulate processing time + time.sleep(1) + + # Create groups + result = create_peer_groups_from_all_users( + n_groups=n_groups, + min_group_size=min_group_size, + max_group_size=max_group_size + ) + + if result["success"]: + st.success(f"Successfully created {len(result['groups'])} peer groups!") + + # Show created groups + for group in result["groups"]: + st.markdown(f"- **{group['name']}**: {group['description']} ({group['member_count']} members)") + else: + st.error(result["message"]) + +def render_peer_groups_page(): + """Main function to render the peer groups page""" + st.title("šŸ‘„ Peer Groups") + st.markdown(""" + Connect with peers who share similar interests and experiences. + Our AI-powered matching helps you find a supportive community. + """) + + # Initialize database if needed + init_peer_matching_db() + + # Check if user is authenticated + if "authenticated" not in st.session_state or not st.session_state.authenticated: + st.warning("Please log in to access peer groups") + return + + # Get user info + user_email = st.session_state.user_email + user_name = st.session_state.user_name + + # Tabs for different sections + tab1, tab2, tab3 = st.tabs(["Your Profile", "Your Groups", "Find Peers"]) + + with tab1: + profile_updated = render_profile_form(user_email, user_name) + if profile_updated: + st.rerun() + + with tab2: + render_peer_groups(user_email) + + with tab3: + render_similar_users(user_email) + + # Admin section for creating groups (only visible to admins) + # In a real app, you'd check if the user is an admin + if user_email == "admin@talkheal.com": + st.markdown("---") + render_group_formation() + +if __name__ == "__main__": + render_peer_groups_page() \ No newline at end of file diff --git a/setup_database.py b/setup_database.py index dbd4f31..62521bd 100644 --- a/setup_database.py +++ b/setup_database.py @@ -12,6 +12,7 @@ import sqlite3 import os from auth.auth_utils import init_db +from core.peer_matching import init_peer_matching_db def setup_journals_db(): """Initialize the journals database""" @@ -44,9 +45,17 @@ def main(): # Initialize journals database try: setup_journals_db() + print("āœ… Journals database initialized successfully") except Exception as e: print(f"āŒ Error initializing journals database: {e}") + # Initialize peer matching database + try: + init_peer_matching_db() + print("āœ… Peer matching database initialized successfully") + except Exception as e: + print(f"āŒ Error initializing peer matching database: {e}") + print("\nšŸŽ‰ Database setup complete!") print("šŸ“ Note: These .db files are automatically created when the app runs.") print("šŸ”’ They are ignored by git to prevent conflicts and protect user data.")