mgbam commited on
Commit
19b059b
·
verified ·
1 Parent(s): be06c46

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +90 -134
app.py CHANGED
@@ -1,22 +1,21 @@
1
  # /home/user/app/app.py
2
  import streamlit as st
3
  from pathlib import Path
4
- from datetime import datetime # For default chat session title
5
- from sqlmodel import select # For SQLModel queries
6
 
7
  from config.settings import settings
8
- # Ensure models are imported correctly based on your __init__.py in models/
9
  from models import (
10
  create_db_and_tables,
11
- get_session_context, # Your SQLModel session context manager
12
  User,
13
  ChatMessage,
14
  ChatSession
15
  )
16
- from models.user import UserCreate # For type hinting
17
  from services.auth import create_user_in_db, authenticate_user
18
  from services.logger import app_logger
19
- from assets.logo import get_logo_path # For consistent logo handling
20
 
21
  # --- Page Configuration ---
22
  st.set_page_config(
@@ -27,209 +26,166 @@ st.set_page_config(
27
  )
28
 
29
  # --- Database Initialization ---
30
- @st.cache_resource # Ensure this runs only once
31
  def init_db():
32
  app_logger.info("Initializing database and tables...")
33
- create_db_and_tables() # This uses SQLModel.metadata.create_all(engine)
34
  app_logger.info("Database initialized.")
35
-
36
  init_db()
37
 
38
- # --- Session State Initialization (Using primitive types for user auth) ---
39
  if 'authenticated_user_id' not in st.session_state:
40
  st.session_state.authenticated_user_id = None
41
  if 'authenticated_username' not in st.session_state:
42
  st.session_state.authenticated_username = None
43
  if 'current_chat_session_id' not in st.session_state:
44
  st.session_state.current_chat_session_id = None
45
- if 'chat_messages' not in st.session_state: # For the current active chat in Consult page
46
  st.session_state.chat_messages = []
 
 
47
 
 
 
 
 
 
 
 
48
 
49
  # --- Authentication Logic ---
50
  def display_login_form():
 
 
 
51
  with st.form("login_form"):
52
  st.subheader("Login")
53
- username_input = st.text_input("Username", key="login_username_input_main_app") # Unique key
54
- password_input = st.text_input("Password", type="password", key="login_password_input_main_app") # Unique key
55
  submit_button = st.form_submit_button("Login")
56
 
57
  if submit_button:
58
  user_object_from_auth = authenticate_user(username_input, password_input)
59
-
60
- if user_object_from_auth: # Indicates successful authentication
61
  st.success(f"Welcome back, {username_input}!")
62
- app_logger.info(f"User '{username_input}' authenticated successfully by authenticate_user.")
63
-
64
  try:
65
- with get_session_context() as db_session: # db_session is a SQLModel Session
66
- # Re-fetch the user to ensure we have a live object for this session
67
- # and to get definitive ID/username if authenticate_user returned a detached object.
68
  statement = select(User).where(User.username == username_input)
69
  live_user = db_session.exec(statement).first()
70
-
71
  if not live_user:
72
- # This is a critical inconsistency if authenticate_user said OK.
73
- st.error("Authentication inconsistency. User details not found after login. Please contact support or try again.")
74
- app_logger.error(f"CRITICAL: User '{username_input}' authenticated but then NOT FOUND in DB by username query.")
75
- # Clear potentially corrupted auth state
76
  st.session_state.authenticated_user_id = None
77
  st.session_state.authenticated_username = None
78
- st.rerun() # Force rerun to show login page
79
  return
80
-
81
- app_logger.info(f"Live user object for '{live_user.username}' (ID: {live_user.id}) obtained in session.")
82
- # Store primitive data in st.session_state
83
  st.session_state.authenticated_user_id = live_user.id
84
  st.session_state.authenticated_username = live_user.username
85
- app_logger.info(f"Stored user ID {live_user.id} and username '{live_user.username}' in Streamlit session state.")
86
-
87
- # Create a new chat session for the user upon login
88
- app_logger.debug(f"Attempting to create new chat session for user_id: {live_user.id}")
89
  new_chat_session = ChatSession(
90
  user_id=live_user.id,
91
  title=f"Session for {live_user.username} - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
92
- # start_time is default_factory=datetime.utcnow in the model
93
  )
94
  db_session.add(new_chat_session)
95
- app_logger.debug("New ChatSession ORM object added to SQLModel session.")
96
-
97
- # Flush the session to persist new_chat_session and get its ID
98
- app_logger.debug("Flushing SQLModel session to assign ID to new_chat_session...")
99
  db_session.flush()
100
- app_logger.debug(f"Session flushed. new_chat_session potential ID: {new_chat_session.id}")
101
-
102
- # Check if ID was assigned; if not, something is wrong with DB/model setup for auto-increment PK.
103
  if new_chat_session.id is None:
104
- app_logger.error("CRITICAL: new_chat_session.id is None after flush. Cannot proceed with session creation.")
105
- raise Exception("Failed to obtain ID for new chat session after database flush.")
106
-
107
- # Refresh the object to load any server-side defaults or confirm state
108
- app_logger.debug(f"Refreshing new_chat_session (ID: {new_chat_session.id}) from database...")
109
  db_session.refresh(new_chat_session)
110
- app_logger.debug("new_chat_session ORM object refreshed.")
111
-
112
- # Store the new chat session ID for use in the Consult page
113
  st.session_state.current_chat_session_id = new_chat_session.id
114
- st.session_state.chat_messages = [] # Clear any old messages for the new session
115
- app_logger.info(f"New chat session (ID: {new_chat_session.id}) created and assigned for user '{live_user.username}'.")
116
- # The commit will be handled by the get_session_context manager upon successful exit of this 'with' block.
117
-
118
- # If all operations within the 'with' block succeed, the context manager will commit.
119
  app_logger.info(f"Post-login setup complete for '{username_input}'. Rerunning app.")
120
- st.rerun() # Rerun to reflect login state and navigate to the default page (e.g., Home)
121
-
122
  except Exception as e:
123
- app_logger.error(f"Error during post-login session setup for user '{username_input}': {e}", exc_info=True)
124
  st.error(f"Could not complete login process: {e}")
125
- # Clear potentially partial auth state to force re-login or prevent inconsistent state
126
  st.session_state.authenticated_user_id = None
127
  st.session_state.authenticated_username = None
128
  st.session_state.current_chat_session_id = None
129
- # No rerun here, let user see the error and try again or contact support.
130
  else:
131
  st.error("Invalid username or password.")
132
  app_logger.warning(f"Failed login attempt for username: {username_input}")
133
 
134
  def display_signup_form():
 
 
135
  with st.form("signup_form"):
136
  st.subheader("Sign Up")
137
- new_username = st.text_input("Choose a Username", key="signup_username_input_main_app") # Unique key
138
- new_email = st.text_input("Email (Optional)", key="signup_email_input_main_app") # Unique key
139
- new_password = st.text_input("Choose a Password", type="password", key="signup_password_input_main_app") # Unique key
140
- confirm_password = st.text_input("Confirm Password", type="password", key="signup_confirm_password_input_main_app") # Unique key
141
  submit_button = st.form_submit_button("Sign Up")
142
-
143
  if submit_button:
144
- if not new_username or not new_password:
145
- st.error("Username and password are required.")
146
- elif len(new_password) < 6: # Example: Minimum password length
147
- st.error("Password must be at least 6 characters long.")
148
- elif new_password != confirm_password:
149
- st.error("Passwords do not match.")
150
  else:
151
- user_data = UserCreate(
152
- username=new_username,
153
- password=new_password,
154
- email=new_email if new_email else None
155
- # full_name can be added if part of UserCreate and signup form
156
- )
157
- # create_user_in_db should handle its own session and return a User object or None.
158
- # It's responsible for hashing password and committing the new user.
159
  user = create_user_in_db(user_data)
160
  if user:
161
- # Use 'new_username' (from form input) for the message
162
  st.success(f"Account created for {new_username}. Please log in.")
163
  app_logger.info(f"Account created for '{new_username}'.")
164
  else:
165
- # create_user_in_db returns None if user exists or another error occurred.
166
- # The function itself should log the specific reason.
167
- st.error("Username or Email might already be taken, or another error occurred during signup. Please check logs or try different credentials.")
168
- app_logger.warning(f"Signup failed for username: '{new_username}'. create_user_in_db returned None.")
169
-
170
- # --- Main App Logic (Checks for authenticated_user_id) ---
171
- if not st.session_state.get("authenticated_user_id"): # Check if user_id is set (i.e., user is logged in)
172
- st.title(f"Welcome to {settings.APP_TITLE}")
173
- st.markdown("Your AI-powered partner for advanced healthcare insights.")
174
-
175
- login_tab, signup_tab = st.tabs(["Login", "Sign Up"])
176
- with login_tab:
177
- display_login_form()
178
- with signup_tab:
179
- display_signup_form()
 
 
 
 
 
180
  else:
181
- # --- User is Authenticated - Display Sidebar and Page Content ---
182
  with st.sidebar:
183
- # Display Logo using get_logo_path for consistency
184
- logo_path_str_sidebar = get_logo_path() # Assumes assets/logo.py is set up
185
  if logo_path_str_sidebar:
186
  logo_file_sidebar = Path(logo_path_str_sidebar)
187
  if logo_file_sidebar.exists():
188
- try:
189
- st.image(str(logo_file_sidebar), width=100) # Adjust width as needed
190
- except Exception as e:
191
- app_logger.warning(f"Could not display logo in sidebar from path '{logo_path_str_sidebar}': {e}")
192
- # else: # get_logo_path should log if file doesn't exist
193
- # app_logger.warning(f"Sidebar logo path from get_logo_path() does not exist: {logo_path_str_sidebar}")
194
- elif settings.APP_TITLE: # Fallback to title if no logo path or logo load fails
195
- st.sidebar.markdown(f"#### {settings.APP_TITLE}")
196
 
197
- # Use the stored primitive username
198
- username_for_display = st.session_state.get("authenticated_username", "User") # Fallback to "User"
199
  st.sidebar.markdown(f"### Welcome, {username_for_display}!")
200
- st.sidebar.markdown("---") # Separator
201
-
202
- # Navigation is handled by Streamlit's multi-page app feature (pages/ directory)
203
- # This app.py acts as the entry point.
204
- # Streamlit automatically lists pages from the `pages/` directory here in the sidebar.
205
 
206
- if st.sidebar.button("Logout", key="sidebar_logout_button_main_app"): # Unique key
207
  logged_out_username = st.session_state.get("authenticated_username", "UnknownUser")
208
  app_logger.info(f"User '{logged_out_username}' logging out.")
209
- # Clear all relevant session state variables
210
- st.session_state.authenticated_user_id = None
211
- st.session_state.authenticated_username = None
212
- st.session_state.current_chat_session_id = None
213
- st.session_state.chat_messages = []
214
- # Potentially clear other page-specific states if needed
215
- # for key in list(st.session_state.keys()):
216
- # if key not in ['rerun_requested', ...]: # preserve essential Streamlit keys
217
- # del st.session_state[key]
218
  st.success("You have been logged out.")
219
  st.rerun()
220
 
221
- # Main content area for app.py when logged in.
222
- # If you have a `pages/1_Home.py`, Streamlit will render that page's content here by default
223
- # when the user navigates to "Home" via the sidebar (or if it's the first page alphabetically).
224
- # The content below in app.py might only be visible briefly or if no page is explicitly selected,
225
- # or if `app.py` is configured as a page itself.
 
 
226
 
227
- # For a typical MPA setup, you might not need much content directly in app.py's main area
228
- # once the user is logged in, as page files will take over.
229
- # A simple instruction or pointer could be useful.
230
- st.sidebar.success("Select a page from the navigation.")
231
- # Optionally, add a default landing message in the main area if no page is loaded yet,
232
- # though Streamlit usually picks the first page in `pages/`.
233
- # st.info("Main application area. Select a page from the sidebar to begin.")
234
 
235
  app_logger.info(f"Streamlit app '{settings.APP_TITLE}' (app.py) processed.")
 
1
  # /home/user/app/app.py
2
  import streamlit as st
3
  from pathlib import Path
4
+ from datetime import datetime
5
+ from sqlmodel import select
6
 
7
  from config.settings import settings
 
8
  from models import (
9
  create_db_and_tables,
10
+ get_session_context,
11
  User,
12
  ChatMessage,
13
  ChatSession
14
  )
15
+ from models.user import UserCreate
16
  from services.auth import create_user_in_db, authenticate_user
17
  from services.logger import app_logger
18
+ from assets.logo import get_logo_path
19
 
20
  # --- Page Configuration ---
21
  st.set_page_config(
 
26
  )
27
 
28
  # --- Database Initialization ---
29
+ @st.cache_resource
30
  def init_db():
31
  app_logger.info("Initializing database and tables...")
32
+ create_db_and_tables()
33
  app_logger.info("Database initialized.")
 
34
  init_db()
35
 
36
+ # --- Session State Initialization ---
37
  if 'authenticated_user_id' not in st.session_state:
38
  st.session_state.authenticated_user_id = None
39
  if 'authenticated_username' not in st.session_state:
40
  st.session_state.authenticated_username = None
41
  if 'current_chat_session_id' not in st.session_state:
42
  st.session_state.current_chat_session_id = None
43
+ if 'chat_messages' not in st.session_state:
44
  st.session_state.chat_messages = []
45
+ if 'user_understands_disclaimer' not in st.session_state:
46
+ st.session_state.user_understands_disclaimer = False
47
 
48
+ # --- Disclaimer Function ---
49
+ def display_disclaimer():
50
+ st.warning(f"**Important Disclaimer:** {settings.MAIN_DISCLAIMER_LONG}")
51
+ st.info(settings.SIMULATION_DISCLAIMER)
52
+ if st.button("I Understand and Agree to Proceed", key="disclaimer_agree_btn"):
53
+ st.session_state.user_understands_disclaimer = True
54
+ st.rerun()
55
 
56
  # --- Authentication Logic ---
57
  def display_login_form():
58
+ # (This function remains the same as the last full app.py provided, which had the fix for
59
+ # `Instance '<ChatSession ...>' is not persistent within this Session`.
60
+ # Ensure that version is used here for robustness.)
61
  with st.form("login_form"):
62
  st.subheader("Login")
63
+ username_input = st.text_input("Username", key="login_username_input_main_app")
64
+ password_input = st.text_input("Password", type="password", key="login_password_input_main_app")
65
  submit_button = st.form_submit_button("Login")
66
 
67
  if submit_button:
68
  user_object_from_auth = authenticate_user(username_input, password_input)
69
+ if user_object_from_auth:
 
70
  st.success(f"Welcome back, {username_input}!")
71
+ app_logger.info(f"User '{username_input}' authenticated successfully.")
 
72
  try:
73
+ with get_session_context() as db_session:
 
 
74
  statement = select(User).where(User.username == username_input)
75
  live_user = db_session.exec(statement).first()
 
76
  if not live_user:
77
+ st.error("Auth inconsistency. User not found post-login.")
78
+ app_logger.error(f"CRITICAL: User '{username_input}' auth OK but NOT FOUND in DB.")
 
 
79
  st.session_state.authenticated_user_id = None
80
  st.session_state.authenticated_username = None
81
+ st.rerun()
82
  return
 
 
 
83
  st.session_state.authenticated_user_id = live_user.id
84
  st.session_state.authenticated_username = live_user.username
85
+ app_logger.info(f"Stored user ID {live_user.id} and username '{live_user.username}' in session state.")
 
 
 
86
  new_chat_session = ChatSession(
87
  user_id=live_user.id,
88
  title=f"Session for {live_user.username} - {datetime.now().strftime('%Y-%m-%d %H:%M')}"
 
89
  )
90
  db_session.add(new_chat_session)
91
+ app_logger.debug("New ChatSession added to SQLModel session.")
 
 
 
92
  db_session.flush()
93
+ app_logger.debug(f"Session flushed. new_chat_session ID: {new_chat_session.id}")
 
 
94
  if new_chat_session.id is None:
95
+ app_logger.error("CRITICAL: new_chat_session.id is None after flush.")
96
+ raise Exception("Failed to obtain ID for new chat session after flush.")
 
 
 
97
  db_session.refresh(new_chat_session)
98
+ app_logger.debug("new_chat_session refreshed.")
 
 
99
  st.session_state.current_chat_session_id = new_chat_session.id
100
+ st.session_state.chat_messages = []
101
+ app_logger.info(f"New chat session (ID: {new_chat_session.id}) created for user '{live_user.username}'.")
 
 
 
102
  app_logger.info(f"Post-login setup complete for '{username_input}'. Rerunning app.")
103
+ st.rerun()
 
104
  except Exception as e:
105
+ app_logger.error(f"Error during post-login session setup for '{username_input}': {e}", exc_info=True)
106
  st.error(f"Could not complete login process: {e}")
 
107
  st.session_state.authenticated_user_id = None
108
  st.session_state.authenticated_username = None
109
  st.session_state.current_chat_session_id = None
 
110
  else:
111
  st.error("Invalid username or password.")
112
  app_logger.warning(f"Failed login attempt for username: {username_input}")
113
 
114
  def display_signup_form():
115
+ # (This function remains largely the same as the last full app.py provided.
116
+ # Ensure it correctly calls your `create_user_in_db` which handles hashing and DB commit.)
117
  with st.form("signup_form"):
118
  st.subheader("Sign Up")
119
+ new_username = st.text_input("Choose a Username", key="signup_username_input_main_app")
120
+ new_email = st.text_input("Email (Optional)", key="signup_email_input_main_app")
121
+ new_password = st.text_input("Choose a Password", type="password", key="signup_password_input_main_app")
122
+ confirm_password = st.text_input("Confirm Password", type="password", key="signup_confirm_password_input_main_app")
123
  submit_button = st.form_submit_button("Sign Up")
 
124
  if submit_button:
125
+ if not new_username or not new_password: st.error("Username and password are required.")
126
+ elif len(new_password) < 6: st.error("Password must be at least 6 characters long.")
127
+ elif new_password != confirm_password: st.error("Passwords do not match.")
 
 
 
128
  else:
129
+ user_data = UserCreate(username=new_username, password=new_password, email=new_email if new_email else None)
 
 
 
 
 
 
 
130
  user = create_user_in_db(user_data)
131
  if user:
 
132
  st.success(f"Account created for {new_username}. Please log in.")
133
  app_logger.info(f"Account created for '{new_username}'.")
134
  else:
135
+ st.error("Username/Email might be taken, or another error occurred. Check logs or try different credentials.")
136
+ app_logger.warning(f"Signup failed for '{new_username}'. create_user_in_db returned None.")
137
+
138
+ # --- Main App Logic ---
139
+ if not st.session_state.get("authenticated_user_id"): # Not logged in
140
+ if not st.session_state.user_understands_disclaimer:
141
+ st.title(f"Welcome to {settings.APP_TITLE}")
142
+ st.markdown("Your AI-powered partner for advanced healthcare insights.")
143
+ st.markdown("---")
144
+ display_disclaimer()
145
+ st.stop() # Stop further execution until disclaimer is agreed to
146
+ else:
147
+ # Disclaimer agreed, show login/signup
148
+ st.title(f"Welcome to {settings.APP_TITLE}")
149
+ st.caption(settings.MAIN_DISCLAIMER_SHORT) # Short disclaimer always visible on login page
150
+ login_tab, signup_tab = st.tabs(["Login", "Sign Up"])
151
+ with login_tab:
152
+ display_login_form()
153
+ with signup_tab:
154
+ display_signup_form()
155
  else:
156
+ # --- User is Authenticated ---
157
  with st.sidebar:
158
+ logo_path_str_sidebar = get_logo_path()
 
159
  if logo_path_str_sidebar:
160
  logo_file_sidebar = Path(logo_path_str_sidebar)
161
  if logo_file_sidebar.exists():
162
+ try: st.image(str(logo_file_sidebar), width=100)
163
+ except Exception as e: app_logger.warning(f"Could not display sidebar logo: {e}")
164
+ elif settings.APP_TITLE: st.sidebar.markdown(f"#### {settings.APP_TITLE}")
 
 
 
 
 
165
 
166
+ username_for_display = st.session_state.get("authenticated_username", "User")
 
167
  st.sidebar.markdown(f"### Welcome, {username_for_display}!")
168
+ st.sidebar.info(settings.MAIN_DISCLAIMER_SHORT) # Constant reminder
169
+ st.sidebar.markdown("---")
 
 
 
170
 
171
+ if st.sidebar.button("Logout", key="sidebar_logout_button_main_app"):
172
  logged_out_username = st.session_state.get("authenticated_username", "UnknownUser")
173
  app_logger.info(f"User '{logged_out_username}' logging out.")
174
+ # Clear all session state, or selectively
175
+ for key in list(st.session_state.keys()): # Clear most session state keys on logout
176
+ if key not in ['query_params']: # Keep essential Streamlit keys if needed
177
+ del st.session_state[key]
178
+ st.session_state.user_understands_disclaimer = False # Require disclaimer agreement again
 
 
 
 
179
  st.success("You have been logged out.")
180
  st.rerun()
181
 
182
+ # Main content area: Streamlit MPA handles this by showing the selected page
183
+ # from the `pages/` directory.
184
+ # If on app.py itself, give a pointer.
185
+ current_page = st.session_state.get('page_script_hash', None)
186
+ if not current_page or current_page == st.elements.utils.calc_page_script_hash('app.py'):
187
+ st.sidebar.success("Select a page from the navigation.")
188
+ st.info("Please select a page from the sidebar (e.g., Home, Consult, Reports) to begin.")
189
 
 
 
 
 
 
 
 
190
 
191
  app_logger.info(f"Streamlit app '{settings.APP_TITLE}' (app.py) processed.")