mgbam commited on
Commit
04ae885
·
verified ·
1 Parent(s): acf2fb5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -29
app.py CHANGED
@@ -37,34 +37,59 @@ if 'chat_messages' not in st.session_state: # For the current active chat
37
  def display_login_form():
38
  with st.form("login_form"):
39
  st.subheader("Login")
40
- username = st.text_input("Username")
41
- password = st.text_input("Password", type="password")
42
  submit_button = st.form_submit_button("Login")
43
 
44
  if submit_button:
45
- user = authenticate_user(username, password)
 
 
 
 
 
 
46
  if user:
47
  st.session_state.authenticated_user = user
 
 
 
48
  st.success(f"Welcome back, {user.username}!")
49
- # Create a new chat session for the user upon login
50
- with get_session_context() as db_session:
51
- new_chat_session = ChatSession(user_id=user.id, title=f"Session for {user.username}")
52
- db_session.add(new_chat_session)
53
- db_session.commit()
54
- db_session.refresh(new_chat_session)
55
- st.session_state.current_chat_session_id = new_chat_session.id
56
- st.session_state.chat_messages = [] # Clear previous messages
57
- st.rerun() # Rerun to reflect login state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  else:
59
  st.error("Invalid username or password.")
60
 
61
  def display_signup_form():
62
  with st.form("signup_form"):
63
  st.subheader("Sign Up")
64
- new_username = st.text_input("Choose a Username")
65
- new_email = st.text_input("Email (Optional)")
66
- new_password = st.text_input("Choose a Password", type="password")
67
- confirm_password = st.text_input("Confirm Password", type="password")
68
  submit_button = st.form_submit_button("Sign Up")
69
 
70
  if submit_button:
@@ -78,20 +103,46 @@ def display_signup_form():
78
  password=new_password,
79
  email=new_email if new_email else None
80
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  user = create_user_in_db(user_data)
82
  if user:
83
- st.success(f"Account created for {user.username}. Please log in.")
84
- # Optionally log them in directly:
 
 
 
 
 
 
85
  # st.session_state.authenticated_user = user
86
  # st.rerun()
 
 
 
87
  else:
88
- st.error("Username might already be taken or an error occurred.")
 
89
 
90
  # --- Main App Logic ---
91
  if not st.session_state.authenticated_user:
92
  st.title(f"Welcome to {settings.APP_TITLE}")
93
  st.markdown("Your AI-powered partner for advanced healthcare insights.")
94
-
95
  login_tab, signup_tab = st.tabs(["Login", "Sign Up"])
96
  with login_tab:
97
  display_login_form()
@@ -102,26 +153,48 @@ else:
102
  # The content of `app.py` typically acts as the "Home" page if no `1_Home.py` exists,
103
  # or it can be used for global elements like a custom sidebar if not using Streamlit's default page navigation.
104
 
105
- # Custom Sidebar for logged-in users (Streamlit handles page navigation automatically)
106
  with st.sidebar:
107
- st.markdown(f"### Welcome, {st.session_state.authenticated_user.username}!")
108
- st.image("assets/logo.png", width=100) # Display logo if available
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  st.markdown("---")
110
-
111
  if st.button("Logout"):
 
112
  st.session_state.authenticated_user = None
113
  st.session_state.current_chat_session_id = None
114
  st.session_state.chat_messages = []
115
  st.success("You have been logged out.")
116
  st.rerun()
117
-
118
  # This content will show if no other page is selected, or if you don't have a 1_Home.py
119
  # If you have 1_Home.py, Streamlit will show that by default after login.
120
- # So, this part might be redundant if 1_Home.py exists and is the intended landing.
121
- st.sidebar.success("Select a page above to get started.")
122
- st.markdown(f"# {settings.APP_TITLE}")
 
123
  st.markdown("Navigate using the sidebar to consult with the AI or view your reports.")
124
  st.markdown("---")
125
  st.info("This is the main application area. If you see this, ensure you have a `pages/1_Home.py` or that this `app.py` is your intended landing page after login.")
126
 
127
- app_logger.info("Streamlit app initialized and running.")
 
 
 
 
 
37
  def display_login_form():
38
  with st.form("login_form"):
39
  st.subheader("Login")
40
+ username_input = st.text_input("Username", key="login_username_input") # Added key for clarity
41
+ password_input = st.text_input("Password", type="password", key="login_password_input") # Added key
42
  submit_button = st.form_submit_button("Login")
43
 
44
  if submit_button:
45
+ # IMPORTANT: The 'user' object returned by authenticate_user needs to have
46
+ # its essential attributes (like id, username) already loaded, or if its session
47
+ # was closed, those attributes should not be in an "expired" state that requires
48
+ # a database refresh. Typically, a query like db.query(User)...first() loads these.
49
+ # If authenticate_user involves a commit and session.expire_on_commit=True (default),
50
+ # it should call db.refresh(user_object) before closing its session and returning.
51
+ user = authenticate_user(username_input, password_input)
52
  if user:
53
  st.session_state.authenticated_user = user
54
+ # Accessing user.username and user.id here.
55
+ # If 'user' is detached and attributes are expired, this could also fail.
56
+ # This implies authenticate_user should return a "usable" object.
57
  st.success(f"Welcome back, {user.username}!")
58
+ try:
59
+ with get_session_context() as db_session:
60
+ # Ensure the user object is attached to the current session if needed,
61
+ # or that its ID is accessible even if detached.
62
+ # user_id = user.id (if user.id is loaded, this is fine)
63
+ # live_user = db_session.merge(user) # if user might be detached but has PK
64
+ # Or better if only ID is needed:
65
+ live_user = db_session.get(User, user.id) # Re-fetch/attach to current session
66
+ if not live_user:
67
+ st.error("User session error. Please log in again.")
68
+ app_logger.error(f"Failed to re-fetch user with id {user.id} in new session.")
69
+ st.session_state.authenticated_user = None # Clear broken state
70
+ st.rerun()
71
+ return
72
+
73
+ new_chat_session = ChatSession(user_id=live_user.id, title=f"Session for {live_user.username}")
74
+ db_session.add(new_chat_session)
75
+ db_session.commit()
76
+ db_session.refresh(new_chat_session) # Refresh new_chat_session
77
+ st.session_state.current_chat_session_id = new_chat_session.id
78
+ st.session_state.chat_messages = [] # Clear previous messages
79
+ st.rerun() # Rerun to reflect login state
80
+ except Exception as e:
81
+ app_logger.error(f"Error creating chat session for user {user.username}: {e}")
82
+ st.error(f"Could not start a new session: {e}")
83
  else:
84
  st.error("Invalid username or password.")
85
 
86
  def display_signup_form():
87
  with st.form("signup_form"):
88
  st.subheader("Sign Up")
89
+ new_username = st.text_input("Choose a Username", key="signup_username_input") # Added key
90
+ new_email = st.text_input("Email (Optional)", key="signup_email_input") # Added key
91
+ new_password = st.text_input("Choose a Password", type="password", key="signup_password_input") # Added key
92
+ confirm_password = st.text_input("Confirm Password", type="password", key="signup_confirm_password_input") # Added key
93
  submit_button = st.form_submit_button("Sign Up")
94
 
95
  if submit_button:
 
103
  password=new_password,
104
  email=new_email if new_email else None
105
  )
106
+ # --- Explanation of the DetachedInstanceError and its handling ---
107
+ # The 'create_user_in_db' function creates a User and commits it to the database.
108
+ # If the SQLAlchemy session used inside 'create_user_in_db' has 'expire_on_commit=True'
109
+ # (which is the default), all attributes of the 'user' object are marked as "expired"
110
+ # after the commit.
111
+ # If 'create_user_in_db' then closes its session and returns this 'user' object,
112
+ # the object becomes "detached".
113
+ # When you later try to access an expired attribute (like 'user.username'),
114
+ # SQLAlchemy attempts to reload it from the database. Since the object is detached
115
+ # (not bound to an active session), this reload fails, raising DetachedInstanceError.
116
+
117
+ # TO FIX ROBUSTLY (in services/auth.py, inside create_user_in_db):
118
+ # After `db_session.commit()`, ensure `db_session.refresh(created_user_object)` is called
119
+ # *before* the session is closed and the object is returned. This loads all attributes
120
+ # from the database, so they are no longer "expired".
121
+
122
  user = create_user_in_db(user_data)
123
  if user:
124
+ # IMMEDIATE FIX for this specific line:
125
+ # Instead of 'user.username' (which might be on a detached, expired instance),
126
+ # use 'new_username', which is the value just submitted by the user and
127
+ # used for creation. This avoids the need to access the potentially problematic 'user' object attribute.
128
+ st.success(f"Account created for {new_username}. Please log in.")
129
+ app_logger.info(f"Account created for {new_username}.")
130
+
131
+ # If you were to enable direct login here:
132
  # st.session_state.authenticated_user = user
133
  # st.rerun()
134
+ # ...then it becomes CRITICAL that 'create_user_in_db' returns a 'user' object
135
+ # whose attributes (like .id, .username) are already loaded and not expired,
136
+ # as explained in the "TO FIX ROBUSTLY" comment above.
137
  else:
138
+ st.error("Username might already be taken or another error occurred during signup.")
139
+ app_logger.warning(f"Failed to create user for username: {new_username}")
140
 
141
  # --- Main App Logic ---
142
  if not st.session_state.authenticated_user:
143
  st.title(f"Welcome to {settings.APP_TITLE}")
144
  st.markdown("Your AI-powered partner for advanced healthcare insights.")
145
+
146
  login_tab, signup_tab = st.tabs(["Login", "Sign Up"])
147
  with login_tab:
148
  display_login_form()
 
153
  # The content of `app.py` typically acts as the "Home" page if no `1_Home.py` exists,
154
  # or it can be used for global elements like a custom sidebar if not using Streamlit's default page navigation.
155
 
156
+ # Custom Sidebar for logged-in users
157
  with st.sidebar:
158
+ # Ensure authenticated_user object is usable. If it was detached with expired attributes
159
+ # upon login, this access could also fail. This reinforces the need for authenticate_user
160
+ # to return a "live" or fully-loaded object.
161
+ try:
162
+ st.markdown(f"### Welcome, {st.session_state.authenticated_user.username}!")
163
+ except AttributeError: # Fallback if username is not accessible
164
+ st.markdown(f"### Welcome!")
165
+ app_logger.error("Could not access username for authenticated_user in sidebar.")
166
+
167
+ if st.session_state.get("SHOW_LOGO_IN_SIDEBAR", True) and settings.LOGO_PATH and Path(settings.LOGO_PATH).exists(): # Example for conditional logo
168
+ try:
169
+ st.image(settings.LOGO_PATH, width=100)
170
+ except Exception as e:
171
+ app_logger.warning(f"Could not load logo from {settings.LOGO_PATH}: {e}")
172
+ elif settings.APP_TITLE: # Fallback to title if no logo
173
+ st.markdown(f"#### {settings.APP_TITLE}")
174
+
175
+
176
  st.markdown("---")
177
+
178
  if st.button("Logout"):
179
+ app_logger.info(f"User {st.session_state.authenticated_user.username} logging out.")
180
  st.session_state.authenticated_user = None
181
  st.session_state.current_chat_session_id = None
182
  st.session_state.chat_messages = []
183
  st.success("You have been logged out.")
184
  st.rerun()
185
+
186
  # This content will show if no other page is selected, or if you don't have a 1_Home.py
187
  # If you have 1_Home.py, Streamlit will show that by default after login.
188
+ st.sidebar.success("Select a page above to get started.") # Or from the main area below if multi-page app
189
+
190
+ # Main area content (could be your "Home" page)
191
+ st.header(f"Dashboard - {settings.APP_TITLE}")
192
  st.markdown("Navigate using the sidebar to consult with the AI or view your reports.")
193
  st.markdown("---")
194
  st.info("This is the main application area. If you see this, ensure you have a `pages/1_Home.py` or that this `app.py` is your intended landing page after login.")
195
 
196
+
197
+ # Ensure Path is imported if used for logo
198
+ from pathlib import Path
199
+
200
+ app_logger.info(f"Streamlit app '{settings.APP_TITLE}' initialized and running.")