File size: 13,748 Bytes
310dade
6145bc0
79d193c
8d6f871
 
e9c724f
864b488
8d6f871
310dade
 
 
 
 
 
 
8d6f871
0b77233
 
8d6f871
864b488
8d6f871
0b77233
 
e9c724f
0b77233
 
 
864b488
8d6f871
 
0b77233
 
310dade
0b77233
8d6f871
0b77233
0a2437f
8d6f871
79d193c
 
 
 
0b77233
 
8d6f871
0b77233
0a2437f
310dade
0b77233
 
 
 
8d6f871
 
0b77233
64f8a92
0b77233
79d193c
e9c724f
8d6f871
310dade
8d6f871
e9c724f
04ae885
310dade
8d6f871
 
310dade
 
e9c724f
04ae885
8d6f871
 
 
 
79d193c
 
8d6f871
04ae885
 
8d6f871
 
79d193c
310dade
8d6f871
 
 
 
 
 
 
 
 
04ae885
8d6f871
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
04ae885
8d6f871
 
 
 
 
 
 
c21fd02
04ae885
8d6f871
79d193c
8d6f871
79d193c
 
8d6f871
 
0b77233
 
e9c724f
64f8a92
0b77233
 
 
8d6f871
 
 
 
0b77233
64f8a92
0b77233
 
 
8d6f871
310dade
0b77233
 
 
 
 
 
 
8d6f871
0b77233
8d6f871
 
310dade
0b77233
8d6f871
310dade
8d6f871
0b77233
8d6f871
 
 
 
0a2437f
8d6f871
 
0b77233
 
8d6f871
0b77233
 
 
 
 
 
8d6f871
0b77233
8d6f871
 
310dade
 
 
e9c724f
8d6f871
e9c724f
310dade
8d6f871
 
 
310dade
8d6f871
 
 
310dade
8d6f871
 
 
 
 
 
 
79d193c
8d6f871
 
79d193c
 
0b77233
 
8d6f871
 
 
 
0b77233
 
8d6f871
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# /home/user/app/app.py
import streamlit as st
from pathlib import Path
from datetime import datetime # For default chat session title
from sqlmodel import select # For SQLModel queries

from config.settings import settings
# Ensure models are imported correctly based on your __init__.py in models/
from models import (
    create_db_and_tables,
    get_session_context, # Your SQLModel session context manager
    User,
    ChatMessage,
    ChatSession
)
from models.user import UserCreate # For type hinting
from services.auth import create_user_in_db, authenticate_user
from services.logger import app_logger
from assets.logo import get_logo_path # For consistent logo handling

# --- Page Configuration ---
st.set_page_config(
    page_title=settings.APP_TITLE,
    page_icon="⚕️",
    layout="wide",
    initial_sidebar_state="expanded"
)

# --- Database Initialization ---
@st.cache_resource # Ensure this runs only once
def init_db():
    app_logger.info("Initializing database and tables...")
    create_db_and_tables() # This uses SQLModel.metadata.create_all(engine)
    app_logger.info("Database initialized.")

init_db()

# --- Session State Initialization (Using primitive types for user auth) ---
if 'authenticated_user_id' not in st.session_state:
    st.session_state.authenticated_user_id = None
if 'authenticated_username' not in st.session_state:
    st.session_state.authenticated_username = None
if 'current_chat_session_id' not in st.session_state:
    st.session_state.current_chat_session_id = None
if 'chat_messages' not in st.session_state: # For the current active chat in Consult page
    st.session_state.chat_messages = []


# --- Authentication Logic ---
def display_login_form():
    with st.form("login_form"):
        st.subheader("Login")
        username_input = st.text_input("Username", key="login_username_input_main_app") # Unique key
        password_input = st.text_input("Password", type="password", key="login_password_input_main_app") # Unique key
        submit_button = st.form_submit_button("Login")

        if submit_button:
            user_object_from_auth = authenticate_user(username_input, password_input)

            if user_object_from_auth: # Indicates successful authentication
                st.success(f"Welcome back, {username_input}!")
                app_logger.info(f"User '{username_input}' authenticated successfully by authenticate_user.")

                try:
                    with get_session_context() as db_session: # db_session is a SQLModel Session
                        # Re-fetch the user to ensure we have a live object for this session
                        # and to get definitive ID/username if authenticate_user returned a detached object.
                        statement = select(User).where(User.username == username_input)
                        live_user = db_session.exec(statement).first()

                        if not live_user:
                            # This is a critical inconsistency if authenticate_user said OK.
                            st.error("Authentication inconsistency. User details not found after login. Please contact support or try again.")
                            app_logger.error(f"CRITICAL: User '{username_input}' authenticated but then NOT FOUND in DB by username query.")
                            # Clear potentially corrupted auth state
                            st.session_state.authenticated_user_id = None
                            st.session_state.authenticated_username = None
                            st.rerun() # Force rerun to show login page
                            return

                        app_logger.info(f"Live user object for '{live_user.username}' (ID: {live_user.id}) obtained in session.")
                        # Store primitive data in st.session_state
                        st.session_state.authenticated_user_id = live_user.id
                        st.session_state.authenticated_username = live_user.username
                        app_logger.info(f"Stored user ID {live_user.id} and username '{live_user.username}' in Streamlit session state.")

                        # Create a new chat session for the user upon login
                        app_logger.debug(f"Attempting to create new chat session for user_id: {live_user.id}")
                        new_chat_session = ChatSession(
                            user_id=live_user.id,
                            title=f"Session for {live_user.username} - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
                            # start_time is default_factory=datetime.utcnow in the model
                        )
                        db_session.add(new_chat_session)
                        app_logger.debug("New ChatSession ORM object added to SQLModel session.")

                        # Flush the session to persist new_chat_session and get its ID
                        app_logger.debug("Flushing SQLModel session to assign ID to new_chat_session...")
                        db_session.flush()
                        app_logger.debug(f"Session flushed. new_chat_session potential ID: {new_chat_session.id}")

                        # Check if ID was assigned; if not, something is wrong with DB/model setup for auto-increment PK.
                        if new_chat_session.id is None:
                            app_logger.error("CRITICAL: new_chat_session.id is None after flush. Cannot proceed with session creation.")
                            raise Exception("Failed to obtain ID for new chat session after database flush.")

                        # Refresh the object to load any server-side defaults or confirm state
                        app_logger.debug(f"Refreshing new_chat_session (ID: {new_chat_session.id}) from database...")
                        db_session.refresh(new_chat_session)
                        app_logger.debug("new_chat_session ORM object refreshed.")

                        # Store the new chat session ID for use in the Consult page
                        st.session_state.current_chat_session_id = new_chat_session.id
                        st.session_state.chat_messages = [] # Clear any old messages for the new session
                        app_logger.info(f"New chat session (ID: {new_chat_session.id}) created and assigned for user '{live_user.username}'.")
                        # The commit will be handled by the get_session_context manager upon successful exit of this 'with' block.

                    # If all operations within the 'with' block succeed, the context manager will commit.
                    app_logger.info(f"Post-login setup complete for '{username_input}'. Rerunning app.")
                    st.rerun() # Rerun to reflect login state and navigate to the default page (e.g., Home)

                except Exception as e:
                    app_logger.error(f"Error during post-login session setup for user '{username_input}': {e}", exc_info=True)
                    st.error(f"Could not complete login process: {e}")
                    # Clear potentially partial auth state to force re-login or prevent inconsistent state
                    st.session_state.authenticated_user_id = None
                    st.session_state.authenticated_username = None
                    st.session_state.current_chat_session_id = None
                    # No rerun here, let user see the error and try again or contact support.
            else:
                st.error("Invalid username or password.")
                app_logger.warning(f"Failed login attempt for username: {username_input}")

def display_signup_form():
    with st.form("signup_form"):
        st.subheader("Sign Up")
        new_username = st.text_input("Choose a Username", key="signup_username_input_main_app") # Unique key
        new_email = st.text_input("Email (Optional)", key="signup_email_input_main_app") # Unique key
        new_password = st.text_input("Choose a Password", type="password", key="signup_password_input_main_app") # Unique key
        confirm_password = st.text_input("Confirm Password", type="password", key="signup_confirm_password_input_main_app") # Unique key
        submit_button = st.form_submit_button("Sign Up")

        if submit_button:
            if not new_username or not new_password:
                st.error("Username and password are required.")
            elif len(new_password) < 6: # Example: Minimum password length
                 st.error("Password must be at least 6 characters long.")
            elif new_password != confirm_password:
                st.error("Passwords do not match.")
            else:
                user_data = UserCreate(
                    username=new_username,
                    password=new_password,
                    email=new_email if new_email else None
                    # full_name can be added if part of UserCreate and signup form
                )
                # create_user_in_db should handle its own session and return a User object or None.
                # It's responsible for hashing password and committing the new user.
                user = create_user_in_db(user_data)
                if user:
                    # Use 'new_username' (from form input) for the message
                    st.success(f"Account created for {new_username}. Please log in.")
                    app_logger.info(f"Account created for '{new_username}'.")
                else:
                    # create_user_in_db returns None if user exists or another error occurred.
                    # The function itself should log the specific reason.
                    st.error("Username or Email might already be taken, or another error occurred during signup. Please check logs or try different credentials.")
                    app_logger.warning(f"Signup failed for username: '{new_username}'. create_user_in_db returned None.")

# --- Main App Logic (Checks for authenticated_user_id) ---
if not st.session_state.get("authenticated_user_id"): # Check if user_id is set (i.e., user is logged in)
    st.title(f"Welcome to {settings.APP_TITLE}")
    st.markdown("Your AI-powered partner for advanced healthcare insights.")

    login_tab, signup_tab = st.tabs(["Login", "Sign Up"])
    with login_tab:
        display_login_form()
    with signup_tab:
        display_signup_form()
else:
    # --- User is Authenticated - Display Sidebar and Page Content ---
    with st.sidebar:
        # Display Logo using get_logo_path for consistency
        logo_path_str_sidebar = get_logo_path() # Assumes assets/logo.py is set up
        if logo_path_str_sidebar:
            logo_file_sidebar = Path(logo_path_str_sidebar)
            if logo_file_sidebar.exists():
                try:
                    st.image(str(logo_file_sidebar), width=100) # Adjust width as needed
                except Exception as e:
                    app_logger.warning(f"Could not display logo in sidebar from path '{logo_path_str_sidebar}': {e}")
            # else: # get_logo_path should log if file doesn't exist
            #    app_logger.warning(f"Sidebar logo path from get_logo_path() does not exist: {logo_path_str_sidebar}")
        elif settings.APP_TITLE: # Fallback to title if no logo path or logo load fails
             st.sidebar.markdown(f"#### {settings.APP_TITLE}")

        # Use the stored primitive username
        username_for_display = st.session_state.get("authenticated_username", "User") # Fallback to "User"
        st.sidebar.markdown(f"### Welcome, {username_for_display}!")
        st.sidebar.markdown("---") # Separator

        # Navigation is handled by Streamlit's multi-page app feature (pages/ directory)
        # This app.py acts as the entry point.
        # Streamlit automatically lists pages from the `pages/` directory here in the sidebar.

        if st.sidebar.button("Logout", key="sidebar_logout_button_main_app"): # Unique key
            logged_out_username = st.session_state.get("authenticated_username", "UnknownUser")
            app_logger.info(f"User '{logged_out_username}' logging out.")
            # Clear all relevant session state variables
            st.session_state.authenticated_user_id = None
            st.session_state.authenticated_username = None
            st.session_state.current_chat_session_id = None
            st.session_state.chat_messages = []
            # Potentially clear other page-specific states if needed
            # for key in list(st.session_state.keys()):
            #     if key not in ['rerun_requested', ...]: # preserve essential Streamlit keys
            #         del st.session_state[key]
            st.success("You have been logged out.")
            st.rerun()

    # Main content area for app.py when logged in.
    # If you have a `pages/1_Home.py`, Streamlit will render that page's content here by default
    # when the user navigates to "Home" via the sidebar (or if it's the first page alphabetically).
    # The content below in app.py might only be visible briefly or if no page is explicitly selected,
    # or if `app.py` is configured as a page itself.

    # For a typical MPA setup, you might not need much content directly in app.py's main area
    # once the user is logged in, as page files will take over.
    # A simple instruction or pointer could be useful.
    st.sidebar.success("Select a page from the navigation.")
    # Optionally, add a default landing message in the main area if no page is loaded yet,
    # though Streamlit usually picks the first page in `pages/`.
    # st.info("Main application area. Select a page from the sidebar to begin.")

app_logger.info(f"Streamlit app '{settings.APP_TITLE}' (app.py) processed.")