mgbam commited on
Commit
8d6f871
·
verified ·
1 Parent(s): e64829c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -55
app.py CHANGED
@@ -1,9 +1,11 @@
1
  # /home/user/app/app.py
2
  import streamlit as st
3
  from pathlib import Path
4
- from sqlmodel import select # <--- IMPORT SELECT FOR SQLMODEL QUERIES
 
5
 
6
  from config.settings import settings
 
7
  from models import (
8
  create_db_and_tables,
9
  get_session_context, # Your SQLModel session context manager
@@ -11,12 +13,12 @@ from models import (
11
  ChatMessage,
12
  ChatSession
13
  )
14
- from models.user import UserCreate
15
  from services.auth import create_user_in_db, authenticate_user
16
  from services.logger import app_logger
17
- from assets.logo import get_logo_path
18
 
19
- # --- Page Configuration --- (Assuming this is already correct)
20
  st.set_page_config(
21
  page_title=settings.APP_TITLE,
22
  page_icon="⚕️",
@@ -24,22 +26,23 @@ st.set_page_config(
24
  initial_sidebar_state="expanded"
25
  )
26
 
27
- # --- Database Initialization --- (Assuming this is already correct)
28
- @st.cache_resource
29
  def init_db():
30
  app_logger.info("Initializing database and tables...")
31
  create_db_and_tables() # This uses SQLModel.metadata.create_all(engine)
32
  app_logger.info("Database initialized.")
 
33
  init_db()
34
 
35
- # --- Session State Initialization --- (Assuming this is already correct)
36
  if 'authenticated_user_id' not in st.session_state:
37
  st.session_state.authenticated_user_id = None
38
  if 'authenticated_username' not in st.session_state:
39
  st.session_state.authenticated_username = None
40
  if 'current_chat_session_id' not in st.session_state:
41
  st.session_state.current_chat_session_id = None
42
- if 'chat_messages' not in st.session_state:
43
  st.session_state.chat_messages = []
44
 
45
 
@@ -47,79 +50,100 @@ if 'chat_messages' not in st.session_state:
47
  def display_login_form():
48
  with st.form("login_form"):
49
  st.subheader("Login")
50
- username_input = st.text_input("Username", key="login_username_input")
51
- password_input = st.text_input("Password", type="password", key="login_password_input")
52
  submit_button = st.form_submit_button("Login")
53
 
54
  if submit_button:
55
  user_object_from_auth = authenticate_user(username_input, password_input)
56
 
57
- if user_object_from_auth:
58
  st.success(f"Welcome back, {username_input}!")
59
- app_logger.info(f"User {username_input} authenticated successfully.")
60
 
61
  try:
62
  with get_session_context() as db_session: # db_session is a SQLModel Session
63
- # --- SQLMODEL QUERY ---
 
64
  statement = select(User).where(User.username == username_input)
65
  live_user = db_session.exec(statement).first()
66
- # --------------------
67
 
68
  if not live_user:
69
- st.error("Authentication inconsistency. User details not found after login. Please contact support.")
70
- app_logger.error(f"CRITICAL: User '{username_input}' authenticated but then not found in DB by username.")
 
 
71
  st.session_state.authenticated_user_id = None
72
  st.session_state.authenticated_username = None
73
- st.rerun()
74
  return
75
 
 
 
76
  st.session_state.authenticated_user_id = live_user.id
77
  st.session_state.authenticated_username = live_user.username
78
- app_logger.info(f"Stored user ID {live_user.id} and username '{live_user.username}' in session state.")
79
-
80
- new_chat_session = ChatSession(user_id=live_user.id, title=f"Session for {live_user.username} ({Path(settings.APP_TITLE).name})")
 
 
 
 
 
 
81
  db_session.add(new_chat_session)
82
- # db_session.commit() # Handled by get_session_context manager
83
- db_session.refresh(new_chat_session) # Refresh after add, before commit if ID needed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  st.session_state.current_chat_session_id = new_chat_session.id
85
- st.session_state.chat_messages = []
86
- app_logger.info(f"New chat session (ID: {new_chat_session.id}) created for user {live_user.username}.")
87
- # Session commit and close handled by get_session_context
88
- st.rerun()
 
 
 
89
 
90
  except Exception as e:
91
- app_logger.error(f"Error during post-login session setup for user {username_input}: {e}", exc_info=True)
92
  st.error(f"Could not complete login process: {e}")
 
93
  st.session_state.authenticated_user_id = None
94
  st.session_state.authenticated_username = None
 
 
95
  else:
96
  st.error("Invalid username or password.")
97
  app_logger.warning(f"Failed login attempt for username: {username_input}")
98
 
99
- # display_signup_form() remains largely the same as it calls create_user_in_db,
100
- # which should internally use SQLModel syntax if it interacts with the DB.
101
-
102
- # --- Main App Logic --- (Remains the same, using updated session state keys)
103
- # ... (rest of app.py as previously provided, ensuring it uses authenticated_user_id and authenticated_username)
104
- # For brevity, I'm omitting the parts of app.py that don't change due to SQLModel query syntax.
105
- # The signup form and the main structure checking st.session_state.get("authenticated_user_id")
106
- # and the sidebar display are correct from the previous version.
107
- # Ensure any other direct DB queries in app.py (if any were added) are also updated.
108
-
109
- # (Make sure the rest of your app.py from the previous good version is here)
110
  def display_signup_form():
111
  with st.form("signup_form"):
112
  st.subheader("Sign Up")
113
- new_username = st.text_input("Choose a Username", key="signup_username_input")
114
- new_email = st.text_input("Email (Optional)", key="signup_email_input")
115
- new_password = st.text_input("Choose a Password", type="password", key="signup_password_input")
116
- confirm_password = st.text_input("Confirm Password", type="password", key="signup_confirm_password_input")
117
  submit_button = st.form_submit_button("Sign Up")
118
 
119
  if submit_button:
120
  if not new_username or not new_password:
121
  st.error("Username and password are required.")
122
- elif len(new_password) < 6:
123
  st.error("Password must be at least 6 characters long.")
124
  elif new_password != confirm_password:
125
  st.error("Passwords do not match.")
@@ -128,45 +152,84 @@ def display_signup_form():
128
  username=new_username,
129
  password=new_password,
130
  email=new_email if new_email else None
 
131
  )
 
 
132
  user = create_user_in_db(user_data)
133
  if user:
 
134
  st.success(f"Account created for {new_username}. Please log in.")
135
- app_logger.info(f"Account created for {new_username}.")
136
  else:
137
- st.error("Username might already be taken or another error occurred during signup.")
138
- app_logger.warning(f"Signup failed for username: {new_username}")
 
 
139
 
140
- if not st.session_state.get("authenticated_user_id"):
 
141
  st.title(f"Welcome to {settings.APP_TITLE}")
142
  st.markdown("Your AI-powered partner for advanced healthcare insights.")
 
143
  login_tab, signup_tab = st.tabs(["Login", "Sign Up"])
144
  with login_tab:
145
  display_login_form()
146
  with signup_tab:
147
  display_signup_form()
148
  else:
 
149
  with st.sidebar:
150
- logo_path_str_sidebar = get_logo_path()
 
151
  if logo_path_str_sidebar:
152
  logo_file_sidebar = Path(logo_path_str_sidebar)
153
  if logo_file_sidebar.exists():
154
  try:
155
- st.image(str(logo_file_sidebar), width=100)
156
  except Exception as e:
157
  app_logger.warning(f"Could not display logo in sidebar from path '{logo_path_str_sidebar}': {e}")
158
- elif settings.APP_TITLE:
 
 
159
  st.sidebar.markdown(f"#### {settings.APP_TITLE}")
160
- username_for_display = st.session_state.get("authenticated_username", "User")
 
 
161
  st.sidebar.markdown(f"### Welcome, {username_for_display}!")
162
- st.sidebar.markdown("---")
163
- if st.sidebar.button("Logout", key="sidebar_logout_button"):
 
 
 
 
 
164
  logged_out_username = st.session_state.get("authenticated_username", "UnknownUser")
165
- app_logger.info(f"User {logged_out_username} logging out.")
 
166
  st.session_state.authenticated_user_id = None
167
  st.session_state.authenticated_username = None
168
  st.session_state.current_chat_session_id = None
169
  st.session_state.chat_messages = []
 
 
 
 
170
  st.success("You have been logged out.")
171
  st.rerun()
172
- app_logger.info(f"Streamlit app '{settings.APP_TITLE}' initialized and running.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
 
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(
23
  page_title=settings.APP_TITLE,
24
  page_icon="⚕️",
 
26
  initial_sidebar_state="expanded"
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
 
 
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.")
 
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.")