diff --git a/TalkHeal.py b/TalkHeal.py index f99cfb6..d2566d4 100644 --- a/TalkHeal.py +++ b/TalkHeal.py @@ -9,23 +9,25 @@ ) import google.generativeai as genai +from core.db import init_db, create_user, get_user_by_username, create_chat, add_message, get_messages + +# --- ENSURE TABLES EXIST BEFORE ANY DB USAGE --- +init_db() + 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_current_time,get_ai_response 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.chat_interface import render_chat_interface from components.emergency_page import render_emergency_page - +from core.db import init_db, get_messages, add_message, create_chat +from core.db import get_user_by_username, create_user # <-- Add this line, adjust module if needed +# --- ENSURE TABLES EXIST BEFORE ANY DB USAGE --- +init_db() # --- 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: @@ -37,8 +39,10 @@ "Substance Use Disorders", "ADHD & Neurodevelopmental", "Personality Disorders", "Sleep Disorders" ] -if "selected_tone" not in st.session_state: - st.session_state.selected_tone = "Compassionate Listener" +if "user_id" not in st.session_state: + st.session_state.user_id = None +if "active_chat_id" not in st.session_state: + st.session_state.active_chat_id = None # --- 2. SET PAGE CONFIG --- @@ -70,22 +74,22 @@ def get_tone_prompt(): return TONE_OPTIONS.get(st.session_state.get("selected_tone", "Compassionate Listener"), TONE_OPTIONS["Compassionate Listener"]) # --- 6. RENDER SIDEBAR --- -render_sidebar() +def render_sidebar(): + from core.db import get_chats_for_user, create_chat + if "user_id" in st.session_state and st.session_state.user_id: + chats = get_chats_for_user(st.session_state.user_id) + for chat in chats: + if st.button(chat["title"], key=f"chat_{chat['id']}"): + st.session_state.active_chat_id = chat["id"] + st.rerun() + if st.button("➕ New Chat"): + chat_id = create_chat(st.session_state.user_id, "Untitled Chat") + st.session_state.active_chat_id = chat_id + st.rerun() # --- 7. PAGE ROUTING --- main_area = st.container() -if not st.session_state.conversations: - saved_conversations = load_conversations() - if saved_conversations: - st.session_state.conversations = saved_conversations - if st.session_state.active_conversation == -1: - st.session_state.active_conversation = 0 - else: - create_new_conversation() - st.session_state.active_conversation = 0 - st.rerun() - # --- 8. RENDER PAGE --- if st.session_state.get("show_emergency_page"): with main_area: @@ -93,11 +97,49 @@ def get_tone_prompt(): else: with main_area: render_header() - st.subheader(f"🗣️ Current Chatbot Tone: **{st.session_state['selected_tone']}**") - render_chat_interface() - handle_chat_input(model, system_prompt=get_tone_prompt()) + render_chat_interface(model) + + # User login/register + from core.db import create_user, get_user_by_username + + if "user_id" not in st.session_state or st.session_state.user_id is None: + st.title("Login / Register") + username = st.text_input("Username") + email = st.text_input("Email") + if st.button("Register/Login"): + user = get_user_by_username(username) + # Add this block to check for existing email + from core.db import get_user_by_email + existing_email_user = get_user_by_email(email) + if existing_email_user and (not user or existing_email_user["id"] != (user["id"] if user else None)): + st.error("This email is already registered. Please use another email or log in with the existing username.") + else: + if not user: + create_user(username, email) + user = get_user_by_username(username) + st.session_state.user_id = user["id"] + st.success(f"Logged in as {username}") + st.rerun() + st.stop() + + # Start new chat + if st.button("➕ New Chat"): + chat_id = create_chat(st.session_state.user_id, "Untitled Chat") + st.session_state.active_chat_id = chat_id + st.experimental_rerun() + + # Send message + user_input = st.text_input("Message", key="message_input") + if st.button("Send") and user_input.strip() and st.session_state.get("active_chat_id"): + add_message(st.session_state.active_chat_id, "user", user_input.strip()) + st.experimental_rerun() + + # Display chat history + if st.session_state.get("active_chat_id"): + messages = get_messages(st.session_state.active_chat_id) + for msg in messages: + st.write(f"{msg['sender']}: {msg['message']}") -# --- 9. SCROLL SCRIPT --- st.markdown(""" -""", unsafe_allow_html=True) \ No newline at end of file +""", unsafe_allow_html=True) \ No newline at end of file diff --git a/components/chat_interface.py b/components/chat_interface.py index 874a329..1dc764c 100644 --- a/components/chat_interface.py +++ b/components/chat_interface.py @@ -1,144 +1,47 @@ import streamlit as st -import streamlit.components.v1 as components -from datetime import datetime -from core.utils import get_current_time, get_ai_response, save_conversations -import requests +from core.db import get_messages, add_message +from core.utils import get_current_time, get_ai_response -# Inject JS to get user's local time zone -def set_user_time_in_session(): - if "user_time_offset" not in st.session_state: - components.html(""" - - """, height=0) +def render_chat_interface(model): + chat_id = st.session_state.get("active_chat_id") + if not chat_id: + st.info("Start a new chat to begin.") + return + messages = get_messages(chat_id) + if not messages: st.markdown(""" - +
+ Hello! I'm TalkHeal, your mental health companion
+ How are you feeling today? You can write it down below or for a fresh start click on "➕ New Chat" 😊 +
""", unsafe_allow_html=True) -set_user_time_in_session() - -# Display chat messages -def render_chat_interface(): - if st.session_state.active_conversation >= 0: - active_convo = st.session_state.conversations[st.session_state.active_conversation] - - if not active_convo["messages"]: + for msg in messages: + if msg["sender"] == "user": st.markdown(f""" -
- Hello! I'm TalkHeal, your mental health companion 🤗
- How are you feeling today? You can write below or start a new topic. -
{get_current_time()}
+
+ {msg["message"]} +
{msg["timestamp"]}
""", unsafe_allow_html=True) - - for msg in active_convo["messages"]: - css_class = "user-message" if msg["sender"] == "user" else "bot-message" + else: st.markdown(f""" -
+
{msg["message"]} -
{msg["time"]}
+
{msg["timestamp"]}
""", unsafe_allow_html=True) -# Handle chat input and generate AI response -def handle_chat_input(model, system_prompt): - if "pre_filled_chat_input" not in st.session_state: - st.session_state.pre_filled_chat_input = "" - initial_value = st.session_state.pre_filled_chat_input - st.session_state.pre_filled_chat_input = "" - with st.form(key="chat_form", clear_on_submit=True): - col1, col2 = st.columns([5, 1]) - with col1: - user_input = st.text_input( - "Share your thoughts...", - key="message_input", - label_visibility="collapsed", - placeholder="Type your message here...", - value=initial_value - ) - with col2: - send_pressed = st.form_submit_button("Send", use_container_width=True) - - if (send_pressed or st.session_state.get("send_chat_message", False)) and user_input.strip(): - if 'send_chat_message' in st.session_state: - st.session_state.send_chat_message = False - - if st.session_state.active_conversation >= 0: - current_time = get_current_time() - active_convo = st.session_state.conversations[st.session_state.active_conversation] - - # Save user message - active_convo["messages"].append({ - "sender": "user", - "message": user_input.strip(), - "time": current_time - }) - - # Set title if it's the first message - if len(active_convo["messages"]) == 1: - title = user_input[:30] + "..." if len(user_input) > 30 else user_input - active_convo["title"] = title - - save_conversations(st.session_state.conversations) - - # Format memory - def format_memory(convo_history, max_turns=10): - context = "" - for msg in convo_history[-max_turns*2:]: # user + bot per turn - sender = "User" if msg["sender"] == "user" else "Bot" - context += f"{sender}: {msg['message']}\n" - return context - - try: - with st.spinner("TalkHeal is thinking..."): - memory = format_memory(active_convo["messages"]) - prompt = f"{system_prompt}\n\n{memory}\nUser: {user_input.strip()}\nBot:" - ai_response = get_ai_response(prompt, model) - - active_convo["messages"].append({ - "sender": "bot", - "message": ai_response, - "time": get_current_time() - }) - - except ValueError as e: - st.error("I'm having trouble understanding your message. Could you please rephrase it?") - active_convo["messages"].append({ - "sender": "bot", - "message": "I'm having trouble understanding your message. Could you please rephrase it?", - "time": get_current_time() - }) - except requests.RequestException as e: - st.error("Network connection issue. Please check your internet connection.") - active_convo["messages"].append({ - "sender": "bot", - "message": "I'm having trouble connecting to my services. Please check your internet connection and try again.", - "time": get_current_time() - }) - except Exception as e: - st.error(f"An unexpected error occurred. Please try again.") - active_convo["messages"].append({ - "sender": "bot", - "message": "I'm having trouble responding right now. Please try again in a moment.", - "time": get_current_time() - }) + user_input = st.text_input("Share your thoughts...", key="message_input", label_visibility="collapsed") + send_pressed = st.form_submit_button("Send") + if send_pressed and user_input.strip(): + add_message(chat_id, "user", user_input.strip()) + with st.spinner("TalkHeal is thinking..."): + context = "\n".join([f"{m['sender'].capitalize()}: {m['message']}" for m in get_messages(chat_id)]) + prompt = context + f"\nUser: {user_input.strip()}\nBot:" + ai_response = get_ai_response(prompt, model) + add_message(chat_id, "bot", ai_response) + st.rerun() - save_conversations(st.session_state.conversations) - st.rerun() diff --git a/components/sidebar.py b/components/sidebar.py index 208d066..6e7406b 100644 --- a/components/sidebar.py +++ b/components/sidebar.py @@ -1,7 +1,7 @@ import streamlit as st import webbrowser from datetime import datetime -from core.utils import create_new_conversation, get_current_time +from core.utils import get_current_time from core.theme import get_current_theme, toggle_theme, set_palette, PALETTES # --- Structured Emergency Resources --- @@ -73,6 +73,8 @@ def render_sidebar(): + from core.db import get_chats_for_user # <-- move import here + """Renders the left and right sidebars.""" with st.sidebar: @@ -85,7 +87,9 @@ def render_sidebar(): st.session_state.send_chat_message = False if st.button("➕ New Chat", key="new_chat", use_container_width=True, type="primary"): - create_new_conversation() + from core.db import create_chat + chat_id = create_chat(st.session_state.user_id, "Untitled Chat") + st.session_state.active_chat_id = chat_id st.session_state.show_quick_start_prompts = True st.rerun() if st.session_state.show_quick_start_prompts: @@ -108,58 +112,6 @@ 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): - is_active = i == st.session_state.active_conversation - button_style_icon = "🟢" if is_active else "📝" - - col1, col2 = st.columns([5, 1]) - with col1: - if st.button( - f"{button_style_icon} {convo['title'][:22]}...", - key=f"convo_{i}", - help=f"Started: {convo['date']}", - use_container_width=True - ): - 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() - - else: - st.warning( - "⚠️ Are you sure you want to delete this conversation?") - col_confirm, col_cancel = st.columns(2) - - if col_confirm.button("Yes, delete", key="confirm_delete"): - del st.session_state.conversations[st.session_state.delete_candidate] - - from core.utils import save_conversations - save_conversations(st.session_state.conversations) - - del st.session_state.delete_candidate - st.session_state.active_conversation = -1 - st.rerun() - - if "cancel_clicked" not in st.session_state: - st.session_state.cancel_clicked = False - - if col_cancel.button("Cancel", key="cancel_delete"): - if not st.session_state.cancel_clicked: - st.session_state.cancel_clicked = True - del st.session_state.delete_candidate - st.rerun() - else: - st.session_state.cancel_clicked = False - - else: - st.info("No conversations yet. Start a new chat!") - - st.markdown("---") - # --- DEDICATED EMERGENCY PAGE BUTTON --- if st.button("🚨 Emergency Help", use_container_width=True, type="secondary"): st.session_state.show_emergency_page = True diff --git a/core/db.py b/core/db.py new file mode 100644 index 0000000..e388aef --- /dev/null +++ b/core/db.py @@ -0,0 +1,87 @@ +import sqlite3 +from contextlib import contextmanager + +DB_PATH = "talkheal.db" + +@contextmanager +def get_db(): + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + try: + yield conn + finally: + conn.commit() + conn.close() + +def init_db(): + with get_db() as db: + db.execute(""" + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE, + email TEXT UNIQUE, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP + )""") + db.execute(""" + CREATE TABLE IF NOT EXISTS chats ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER, + title TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(user_id) REFERENCES users(id) + )""") + db.execute(""" + CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + chat_id INTEGER, + sender TEXT, + message TEXT, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(chat_id) REFERENCES chats(id) + )""") + +def create_user(username, email): + with get_db() as db: + db.execute( + "INSERT INTO users (username, email) VALUES (?, ?)", + (username, email) + ) + +def get_user_by_username(username): + with get_db() as db: + cur = db.execute( + "SELECT * FROM users WHERE username = ?", + (username,) + ) + return cur.fetchone() + +def get_user_by_email(email): + with get_db() as db: + cur = db.execute( + "SELECT * FROM users WHERE email = ?", + (email,) + ) + return cur.fetchone() + +def create_chat(user_id, title): + with get_db() as db: + cur = db.execute( + "INSERT INTO chats (user_id, title) VALUES (?, ?)", + (user_id, title) + ) + return cur.lastrowid + +def add_message(chat_id, sender, message): + with get_db() as db: + db.execute( + "INSERT INTO messages (chat_id, sender, message) VALUES (?, ?, ?)", + (chat_id, sender, message) + ) + +def get_messages(chat_id): + with get_db() as db: + cur = db.execute( + "SELECT * FROM messages WHERE chat_id = ? ORDER BY timestamp ASC", + (chat_id,) + ) + return cur.fetchall() \ No newline at end of file diff --git a/talkheal.db b/talkheal.db new file mode 100644 index 0000000..6d683fe Binary files /dev/null and b/talkheal.db differ