Innovator89 commited on
Commit
034f146
·
verified ·
1 Parent(s): b937b33

Update user_management.py

Browse files
Files changed (1) hide show
  1. user_management.py +1020 -69
user_management.py CHANGED
@@ -1,75 +1,1026 @@
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
- from datetime import datetime
3
- # Import database functions
4
- from database import register_user, user_exists, check_user_credentials, save_user_history
5
-
6
- def show_user_management():
7
- """Handle user registration and login with a single form toggle."""
8
- st.sidebar.header("User Management 🧑‍⚕️")
9
-
10
- if "user_id" not in st.session_state:
11
- st.session_state.user_id = None
12
-
13
- if st.session_state.user_id:
14
- st.sidebar.write(f"Welcome, {st.session_state.user_id}!")
15
- if st.sidebar.button("Logout", key="logout_button"):
16
- st.session_state.user_id = None
17
- st.success("Logged out successfully!")
18
- st.rerun() # Rerun to update UI immediately
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  else:
20
- # Toggle between sign-in and register
21
- form_type = st.sidebar.selectbox("Choose Action", ["Sign In", "Register"], key="form_type")
22
-
23
- if form_type == "Sign In":
24
- with st.sidebar.form("login_form"):
25
- st.markdown("### Sign In")
26
- login_user_id = st.text_input("User ID", key="login_user_id")
27
- login_password = st.text_input("Password", type="password", key="login_password")
28
- submit_login = st.form_submit_button("Sign In")
29
-
30
- if submit_login:
31
- if not login_user_id or not login_password:
32
- st.sidebar.error("Please enter both User ID and Password.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  else:
34
- with st.spinner("Signing in..."): # Spinner for login
35
- if check_user_credentials(login_user_id, login_password):
36
- st.session_state.user_id = login_user_id
37
- st.sidebar.success("Signed in successfully!")
38
- st.rerun() # Rerun to update UI with logged-in state
39
- else:
40
- st.sidebar.error("Invalid User ID or Password.")
41
-
42
- else: # Register
43
- with st.sidebar.form("register_form"):
44
- st.markdown("### Register")
45
- reg_user_id = st.text_input("Choose a unique User ID", key="reg_user_id")
46
- full_name = st.text_input("Full Name", key="reg_full_name")
47
- dob = st.date_input("Date of Birth", min_value=datetime(1900, 1, 1), max_value=datetime.today(), key="reg_dob")
48
- email = st.text_input("Email (optional)", key="reg_email")
49
- reg_password = st.text_input("Password", type="password", key="reg_password")
50
- submit_register = st.form_submit_button("Register")
51
-
52
- if submit_register:
53
- if not reg_user_id or not reg_password or not full_name:
54
- st.sidebar.error("User ID, Full Name, and Password are required.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  else:
56
- with st.spinner("Checking User ID..."): # Spinner for user_exists check
57
- if user_exists(reg_user_id):
58
- st.sidebar.error("User ID already taken. Please choose another.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  else:
60
- with st.spinner("Registering user..."): # Spinner for registration
61
- # Pass dob as a string, as database functions expect string or date object depending on implementation
62
- # datetime.date object is passed to register_user function
63
- if register_user(reg_user_id, full_name, dob, email or None, reg_password):
64
- st.session_state.user_id = reg_user_id
65
- st.sidebar.success("Registered successfully! You are now logged in.")
66
- st.rerun() # Rerun to update UI with logged-in state
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  else:
68
- st.sidebar.error("Registration failed. Please try again.")
69
-
70
- def save_history_if_logged_in(symptoms: str, predicted_diseases: str):
71
- """Save history if user is logged in."""
72
- if st.session_state.get("user_id"):
73
- # This call is now correctly passing all required arguments to database.save_user_history
74
- with st.spinner("Saving history..."): # Spinner for saving history
75
- save_user_history(st.session_state.user_id, symptoms, predicted_diseases)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import os
3
+ import re
4
+ from datetime import datetime, timedelta
5
+
6
+ import numpy as np
7
+ import pandas as pd
8
+ import requests
9
  import streamlit as st
10
+ from dotenv import load_dotenv
11
+ from fuzzywuzzy import fuzz # Make sure fuzzywuzzy is installed (pip install fuzzywuzzy python-levenshtein)
12
+ from groq import Groq
13
+ from reportlab.lib.pagesizes import letter
14
+ from reportlab.pdfgen import canvas
15
+
16
+ # Import database and user management functions
17
+ from database import get_user_history
18
+ from user_management import show_user_management, save_history_if_logged_in
19
+ from utils import extract_keyword # Ensure utils.py has the extract_keyword function
20
+
21
+ # Load environment variables from .env.local for local development.
22
+ # On Hugging Face Spaces, st.secrets will be used.
23
+ load_dotenv(dotenv_path=".env.local")
24
+
25
+ # --- Configuration and Initial Setup (MUST BE FIRST) ---
26
+ st.set_page_config(page_title="MediBot - Health Assistant", page_icon="🏥", layout="wide")
27
+
28
+ # Initialize Groq client
29
+ groq_client = None
30
+ GROQ_AVAILABLE = False
31
+ try:
32
+ # Prefer st.secrets for Hugging Face Spaces deployment
33
+ GROQ_API_KEY = st.secrets.get("GROQ_API_KEY")
34
+ if not GROQ_API_KEY:
35
+ # Fallback to environment variable for local development if not in secrets
36
+ # You might not want to write raw file content to the app in production
37
+ # f = open(".env", "r")
38
+ # st.write(f.read())
39
+ # f.close()
40
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
41
+ if GROQ_API_KEY:
42
+ groq_client = Groq(api_key=GROQ_API_KEY)
43
+ GROQ_AVAILABLE = True
44
+ else:
45
+ st.error("Groq API Key not found. Groq chatbot will not be available.")
46
+ except Exception as e:
47
+ st.error(f"Error initializing Groq client: {e}. Groq chatbot will not be available.")
48
+
49
+ # Initialize Hugging Face Inference API client details for DATEXIS/CORe-clinical-diagnosis-prediction
50
+ HF_MODEL_AVAILABLE = False
51
+ HF_API_TOKEN = None
52
+ try:
53
+ # Assuming 'med_model' is the name of your Hugging Face API key in st.secrets
54
+ HF_API_TOKEN = st.secrets.get("med_model")
55
+ if not HF_API_TOKEN:
56
+ # Fallback to environment variable for local development
57
+ HF_API_TOKEN = os.getenv("MED_MODEL") # Using MED_MODEL for consistency with environment variables
58
+ if HF_API_TOKEN:
59
+ HF_MODEL_AVAILABLE = True
60
  else:
61
+ st.warning("Hugging Face 'med_model' API Key not found. Clinical diagnosis assessment will not be available.")
62
+ except Exception as e:
63
+ st.warning(f"Error retrieving Hugging Face API key: {e}. Clinical diagnosis assessment will not be available.")
64
+
65
+ # Initialize session state variables
66
+ if "chat_history" not in st.session_state:
67
+ st.session_state.chat_history = []
68
+ if "feedback" not in st.session_state:
69
+ st.session_state.feedback = []
70
+ if "show_welcome" not in st.session_state:
71
+ st.session_state.show_welcome = True
72
+ if "chat_input_value" not in st.session_state: # To clear the text area after submission
73
+ st.session_state.chat_input_value = ""
74
+ if "last_chat_response" not in st.session_state: # To persist the last chat response
75
+ st.session_state.last_chat_response = ""
76
+ if "feedback_input" not in st.session_state: # For clearing feedback text area
77
+ st.session_state.feedback_input = ""
78
+ # Ensure user_id is initialized for saving history
79
+ if "user_id" not in st.session_state:
80
+ st.session_state.user_id = None
81
+ if "logged_in_user" not in st.session_state:
82
+ st.session_state.logged_in_user = None
83
+
84
+ # --- HARDCODED DATA LOADING FROM CSVs ---
85
+ @st.cache_data # Cache this function to avoid reloading data on every rerun
86
+ def load_csv_data():
87
+ try:
88
+ # These paths assume the CSVs are directly in the same directory as app.py
89
+ dataset_df = pd.read_csv('dataset.csv').fillna('') # Fill NaN with empty string
90
+ description_df = pd.read_csv('symptom_Description.csv').fillna('')
91
+ precaution_df = pd.read_csv('symptom_precaution.csv').fillna('')
92
+ severity_df = pd.read_csv('Symptom-severity.csv').fillna('') # Load symptom severity
93
+ # Prepare data for quick lookup
94
+ # Dataset mapping diseases to their symptoms
95
+ disease_symptoms_map = {}
96
+ for index, row in dataset_df.iterrows():
97
+ disease = row['Disease']
98
+ # Get all symptoms for this disease, filtering out empty strings and 'Disease' column itself
99
+ symptoms = [s.strip().replace('_', ' ') for s in row.values[1:] if s.strip()]
100
+ disease_symptoms_map[disease] = symptoms
101
+ # Disease descriptions map
102
+ disease_description_map = {row['Disease']: row['Description'] for index, row in description_df.iterrows()}
103
+ # Disease precautions map
104
+ disease_precaution_map = {row['Disease']: [p.strip() for p in row.values[1:] if p.strip()] for index, row in precaution_df.iterrows()}
105
+ # Symptom severity map
106
+ # Ensure symptom names are consistent (e.g., lowercase and spaces instead of underscores)
107
+ symptom_severity_map = {row['Symptom'].strip().replace('_', ' ').lower(): row['weight'] for index, row in severity_df.iterrows()}
108
+ # Extract all unique symptoms for the multiselect
109
+ all_unique_symptoms = sorted(list(set(symptom for symptoms_list in disease_symptoms_map.values() for symptom in symptoms_list)))
110
+ return disease_symptoms_map, disease_description_map, disease_precaution_map, all_unique_symptoms, symptom_severity_map
111
+ except FileNotFoundError as e:
112
+ st.error(f"Error: Required CSV file not found. Make sure 'dataset.csv', 'symptom_Description.csv', 'symptom_precaution.csv', and 'Symptom-severity.csv' are in the correct directory. Details: {e}")
113
+ st.stop() # Stop the app if crucial files are missing
114
+ except Exception as e:
115
+ st.error(f"Error loading CSV data: {e}")
116
+ st.stop() # Stop the app if data loading fails
117
+
118
+ disease_symptoms_map, disease_description_map, disease_precaution_map, hardcoded_symptoms, symptom_severity_map = load_csv_data()
119
+
120
+ # --- Custom CSS for extensive UI improvements ---
121
+ st.markdown("""
122
+ <style>
123
+ /* Basic Page Layout & Background */
124
+ .stApp {
125
+ background-color: #f8f9fa; /* Very light grey */
126
+ font-family: 'Arial', sans-serif; /* Modern sans-serif font */
127
+ color: #343a40; /* Darker grey for primary text */
128
+ }
129
+ /* Main Container Styling (for sections like Home, History) */
130
+ /* Streamlit's main content block often has a data-testid="stVerticalBlock" */
131
+ .stApp > header, .stApp > div:first-child > div:first-child > div:first-child {
132
+ /* This targets the container that usually holds the main content in Streamlit */
133
+ padding: 2.5rem; /* Increased padding */
134
+ border-radius: 12px; /* More rounded corners */
135
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); /* Stronger, softer shadow */
136
+ margin-top: 2rem; /* Space from top elements */
137
+ margin-bottom: 2rem;
138
+ background-color: #ffffff; /* White background */
139
+ }
140
+
141
+ /* Headers */
142
+ h1, h2, h3, h4, h5, h6 {
143
+ color: #004d99; /* A professional darker blue */
144
+ font-weight: 700; /* Bold headers */
145
+ margin-top: 1.5em;
146
+ margin-bottom: 0.8em;
147
+ }
148
+ h1 { font-size: 2.8em; text-align: center; color: #003366; } /* Larger, darker blue for main title */
149
+ h2 { font-size: 2.2em; border-bottom: 2px solid #e0e7ee; padding-bottom: 0.5em; margin-bottom: 1em; } /* Subtle line under h2 */
150
+ h3 { font-size: 1.6em; }
151
+
152
+ /* Main Tabs Styling (Home, History, Feedback, About, Insights) */
153
+ .stTabs [data-baseweb="tab-list"] {
154
+ gap: 20px; /* More space between tabs */
155
+ margin-bottom: 30px; /* More space below tabs */
156
+ justify-content: center; /* Center the main tabs */
157
+ flex-wrap: wrap; /* Allow tabs to wrap on smaller screens */
158
+ }
159
+ .stTabs [data-baseweb="tab"] {
160
+ height: 60px; /* Taller tabs */
161
+ padding: 15px 30px; /* More padding */
162
+ background-color: #e9f0f6; /* Light blue-grey background for tabs */
163
+ border-radius: 12px 12px 0 0; /* Rounded top corners */
164
+ font-size: 1.25em; /* Larger font size */
165
+ font-weight: 600; /* Semi-bold */
166
+ color: #4a6a8c; /* Muted blue text color */
167
+ border: none; /* Remove default border */
168
+ border-bottom: 4px solid transparent; /* Thicker highlight bottom border */
169
+ transition: all 0.3s ease-in-out; /* Smooth transitions */
170
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05); /* Subtle shadow */
171
+ outline: none !important; /* Explicitly remove outline */
172
+ text-align: center; /* Center text within tab */
173
+ min-width: 120px; /* Ensure a minimum width for tabs on mobile */
174
+ }
175
+ .stTabs [data-baseweb="tab"]:hover {
176
+ background-color: #dbe4ee; /* Slightly darker blue-grey on hover */
177
+ color: #004d99; /* Darker blue text on hover */
178
+ transform: translateY(-2px); /* Slight lift effect */
179
+ outline: none !important; /* Ensure no outline on hover either */
180
+ }
181
+ /* Updated styling for selected main tabs */
182
+ .stTabs [data-baseweb="tab"][aria-selected="true"] {
183
+ background-color: #004d99; /* Sober darker blue for selected tab */
184
+ color: white; /* White text for selected tab */
185
+ border-bottom: 4px solid #004d99; /* Keep the consistent bottom border */
186
+ box-shadow: 0 -4px 15px rgba(0, 0, 0, 0.15); /* More pronounced shadow for selected */
187
+ outline: none !important; /* Ensure no outline when selected */
188
+ }
189
+ /* Ensure no outline on focus for tabs */
190
+ .stTabs [data-baseweb="tab"]:focus {
191
+ outline: none !important;
192
+ box-shadow: 0 0 0 0.25rem rgba(0, 123, 255, 0.25) !important; /* Subtle focus glow if needed */
193
+ }
194
+
195
+ /* Nested Tabs Styling (Symptom Checker, Chat with MediBot) */
196
+ /* This targets tabs that are children of other tabs, making them smaller */
197
+ .stTabs [data-baseweb="tab-list"]:has(.stTabs [data-baseweb="tab-list"]) [data-baseweb="tab"],
198
+ .stTabs [data-baseweb="tab-list"] [role="tablist"] [data-baseweb="tab"] { /* More robust selector for nested tabs */
199
+ height: 45px; /* Slightly smaller height for sub-tabs */
200
+ padding: 10px 20px; /* Slightly smaller padding */
201
+ font-size: 1.1em;
202
+ background-color: #f0f5f9; /* Lighter blue-grey for sub-tabs */
203
+ border-radius: 10px; /* Fully rounded corners for sub-tabs */
204
+ border: 1px solid #d8e2ed;
205
+ margin: 0 8px; /* Small margin between sub-tabs */
206
+ box-shadow: none; /* No individual shadow for sub-tabs */
207
+ color: #5f7a96;
208
+ outline: none !important; /* Remove outline on focus */
209
+ min-width: unset; /* Remove min-width for sub-tabs */
210
+ }
211
+ .stTabs [data-baseweb="tab-list"]:has(.stTabs [data-baseweb="tab-list"]) [data-baseweb="tab"]:hover,
212
+ .stTabs [data-baseweb="tab-list"] [role="tablist"] [data-baseweb="tab"]:hover {
213
+ background-color: #e3ecf5;
214
+ color: #004d99;
215
+ transform: none; /* No lift for sub-tabs */
216
+ outline: none !important; /* Ensure no outline on hover either */
217
+ }
218
+ /* Updated styling for selected nested tabs */
219
+ .stTabs [data-baseweb="tab-list"]:has(.stTabs [data-baseweb="tab-list"]) [aria-selected="true"],
220
+ .stTabs [data-baseweb="tab-list"] [role="tablist"] [aria-selected="true"] {
221
+ background-color: #0056b3; /* Slightly darker blue for selected sub-tab */
222
+ color: white;
223
+ border: 1px solid #0056b3;
224
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15); /* Subtle shadow for selected sub-tab */
225
+ outline: none !important; /* Ensure no outline when selected */
226
+ }
227
+
228
+ /* General Button Styling */
229
+ .stButton>button {
230
+ background-color: #007bff; /* Primary blue button */
231
+ color: white;
232
+ border-radius: 10px; /* More rounded */
233
+ padding: 12px 28px; /* More padding */
234
+ font-weight: 600; /* Semi-bold */
235
+ border: none;
236
+ cursor: pointer;
237
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1); /* Softer, more diffuse shadow */
238
+ transition: all 0.3s ease-in-out; /* Smooth transitions */
239
+ font-size: 1.05em; /* Slightly larger text */
240
+ outline: none; /* Remove outline on focus */
241
+ width: auto; /* Allow buttons to size naturally, but consider max-width on mobile */
242
+ min-width: 100px; /* Ensure minimum tappable area */
243
+ }
244
+ .stButton>button:hover {
245
+ background-color: #0056b3; /* Darker blue on hover */
246
+ box-shadow: 0 8px 20px rgba(0,0,0,0.15); /* Larger shadow on hover */
247
+ transform: translateY(-3px); /* More pronounced lift */
248
+ outline: none; /* Ensure no outline on hover either */
249
+ }
250
+ /* Full width buttons in sidebar for better mobile experience */
251
+ .sidebar .stButton>button {
252
+ width: 100%; /* Full width buttons in sidebar */
253
+ margin-bottom: 10px;
254
+ }
255
+
256
+
257
+ /* Input Fields, Selects, etc. */
258
+ .stTextInput>div>div>input,
259
+ .stTextArea>div>div>textarea, /* Target textarea as well */
260
+ .stDateInput>div>div>input,
261
+ .stMultiSelect>div>div {
262
+ border-radius: 10px; /* More rounded */
263
+ border: 1px solid #ced4da; /* Light grey border */
264
+ padding: 10px 15px; /* More padding */
265
+ box-shadow: inset 0 1px 4px rgba(0,0,0,0.05); /* Inner shadow */
266
+ transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
267
+ background-color: #fefefe; /* Slightly off-white background */
268
+ outline: none; /* Remove default outline */
269
+ }
270
+ .stTextInput>div>div>input:focus,
271
+ .stTextArea>div>div>textarea:focus,
272
+ .stDateInput>div>div>input:focus,
273
+ .stMultiSelect>div>div:focus-within {
274
+ border-color: #007bff; /* Primary blue border on focus */
275
+ box-shadow: 0 0 0 0.25rem rgba(0,123,255,.25); /* Focus glow */
276
+ outline: none; /* Remove default outline */
277
+ }
278
+ /* Specific styling for the multiselect selected items */
279
+ .stMultiSelect div[data-baseweb="tag"] {
280
+ background-color: #e0f2f7 !important; /* Light blue tag background */
281
+ color: #004d99 !important; /* Dark blue tag text */
282
+ border-radius: 5px !important;
283
+ padding: 5px 10px !important;
284
+ margin: 2px !important;
285
+ }
286
+ /* Expander (for disease info) */
287
+ .stExpander {
288
+ border: 1px solid #e0e7ee; /* Lighter grey border */
289
+ border-radius: 12px; /* More rounded */
290
+ margin-bottom: 15px; /* More space below */
291
+ background-color: #ffffff; /* White background */
292
+ box-shadow: 0 4px 12px rgba(0,0,0,0.05); /* Softer shadow */
293
+ overflow: hidden; /* Ensure rounded corners clip content */
294
+ }
295
+ .stExpander > div > div > .streamlit-expanderHeader {
296
+ background-color: #f0f5f9; /* Light blue header */
297
+ padding: 15px 20px;
298
+ font-weight: 600;
299
+ font-size: 1.1em;
300
+ color: #004d99;
301
+ border-bottom: 1px solid #e0e7ee;
302
+ outline: none !important; /* Remove outline on focus */
303
+ }
304
+ .stExpander > div > div > .streamlit-expanderHeader:focus {
305
+ outline: none !important; /* Ensure no outline on focus */
306
+ box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important; /* Subtle focus ring */
307
+ }
308
+ .stExpander > div > div > .streamlit-expanderContent {
309
+ padding: 20px; /* Increased padding inside */
310
+ }
311
+ /* Sidebar Styling */
312
+ /* Streamlit's sidebar elements are usually inside a div with role="complementary" */
313
+ .st-emotion-cache-vk33as { /* This targets the Streamlit sidebar div with padding */
314
+ background-color: #ffffff; /* White sidebar background */
315
+ padding: 25px;
316
+ border-radius: 12px;
317
+ box-shadow: 0 8px 24px rgba(0,0,0,0.08); /* Consistent shadow */
318
+ margin-top: 2rem;
319
+ }
320
+ .sidebar .stButton>button {
321
+ width: 100%; /* Full width buttons in sidebar */
322
+ margin-bottom: 10px;
323
+ }
324
+ .sidebar h2, .sidebar h3 {
325
+ color: #004d99;
326
+ text-align: center;
327
+ margin-bottom: 1.5em;
328
+ }
329
+ .sidebar .stTextInput, .sidebar .stDateInput, .sidebar .stSelectbox {
330
+ margin-bottom: 1em; /* Space between sidebar inputs */
331
+ }
332
+ /* Alerts */
333
+ .stAlert {
334
+ border-radius: 10px; /* Rounded corners for alerts */
335
+ padding: 15px;
336
+ font-size: 1.05em;
337
+ }
338
+ .stAlert.st-success { background-color: #d4edda; color: #155724; border-color: #c3e6cb; }
339
+ .stAlert.st-info { background-color: #d1ecf1; color: #0c5460; border-color: #bee5eb; }
340
+ .stAlert.st-warning { background-color: #fff3cd; color: #856404; border-color: #ffeeba; }
341
+ .stAlert.st-error { background-color: #f8d7da; color: #721c24; border-color: #f5c6cb; }
342
+ /* Spinners */
343
+ .stSpinner { color: #007bff; } /* Spinner color matching primary button */
344
+
345
+ /* Disclaimer Box - using a more reliable target */
346
+ div[data-testid="stVerticalBlock"] > div:first-child > div:has(p > strong:contains("Disclaimer")) {
347
+ background-color: #fff8e1; /* Light yellow for warnings/disclaimers */
348
+ border: 1px solid #ffeeba;
349
+ padding: 1.5rem;
350
+ border-radius: 10px;
351
+ margin-bottom: 2rem;
352
+ color: #856404;
353
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
354
+ }
355
+ /* This targets the specific disclaimer block by looking for its content */
356
+ .stAlert[data-testid="stAlert"] p strong:contains("Important Disclaimer") {
357
+ color: #664d03;
358
+ }
359
+
360
+ /* General text improvements */
361
+ p {
362
+ line-height: 1.7;
363
+ margin-bottom: 1em;
364
+ }
365
+ ul, ol {
366
+ margin-bottom: 1em;
367
+ padding-left: 25px;
368
+ }
369
+ li {
370
+ margin-bottom: 0.5em;
371
+ }
372
+ /* Increased font size for history expander titles */
373
+ .stExpander > div > div > .streamlit-expanderHeader {
374
+ font-size: 1.2em; /* Increased font size */
375
+ }
376
+
377
+ /* Media queries for specific mobile adjustments if needed (example) */
378
+ @media (max-width: 768px) {
379
+ h1 {
380
+ font-size: 2.2em; /* Slightly smaller H1 on mobile */
381
+ }
382
+ h2 {
383
+ font-size: 1.8em; /* Slightly smaller H2 on mobile */
384
+ }
385
+ .stTabs [data-baseweb="tab"] {
386
+ font-size: 1em; /* Smaller tab font size on mobile */
387
+ padding: 10px 15px; /* Reduced padding for tabs on mobile */
388
+ height: auto; /* Allow height to adjust */
389
+ min-width: unset; /* Remove min-width to allow more flexibility */
390
+ flex: 1 1 auto; /* Allow tabs to grow and shrink more flexibly */
391
+ }
392
+ .stTabs [data-baseweb="tab-list"] {
393
+ gap: 10px; /* Smaller gap between tabs on mobile */
394
+ }
395
+ /* Make multiselect items slightly smaller on mobile for better fit */
396
+ .stMultiSelect div[data-baseweb="tag"] {
397
+ font-size: 0.9em !important;
398
+ padding: 3px 8px !important;
399
+ }
400
+ }
401
+ </style>
402
+ """, unsafe_allow_html=True)
403
+
404
+ # --- Groq API Response Function (updated system prompt) ---
405
+ def get_groq_response(user_query, severity_label="Undetermined Severity", model="llama-3.3-70b-versatile"):
406
+ """
407
+ Function to get a response from Groq API for health questions.
408
+ Augments the system prompt with severity information if available.
409
+ """
410
+ if not GROQ_AVAILABLE or groq_client is None:
411
+ return "AI assistant is not available."
412
+
413
+ # Augment the system prompt with severity information from clinical diagnosis model
414
+ # Note: 'severity_label' comes from the symptom checker. If asking a direct question,
415
+ # it might default to "Undetermined Severity" unless explicitly passed from a prior analysis.
416
+ system_prompt = (
417
+ "You are an experienced physician specialized in diagnosis and clinical decision-making. "
418
+ "Your primary role is to analyze presented symptoms or health queries and provide a differential diagnosis along with evidence-based recommendations for next steps. "
419
+ f"Based on initial analysis, the perceived severity of the user's condition is: {severity_label}. "
420
+ "Adjust your tone and recommendations accordingly, emphasizing urgency if severity is 'High Severity'.\n\n"
421
+ "When a user describes symptoms or asks a health question, you should:\n\n"
422
+ "1. **Prioritized Differential Diagnosis**: Provide a prioritized list of possible diagnoses based on the information given, indicating their relative likelihood or confidence (e.g., 'most likely', 'possible', 'less likely').\n"
423
+ "2. **Reasoning**: Briefly explain the clinical reasoning for each diagnosis, referencing common clinical features, pathophysiology, or typical presentations.\n"
424
+ "3. **Recommended Investigations**: Suggest appropriate next diagnostic tests or investigations to confirm or rule out these conditions.\n"
425
+ "4. **Initial Management/Treatment**: Propose evidence-based initial management or general treatment options (e.g., symptomatic relief, lifestyle changes, over-the-counter suggestions).\n"
426
+ "5. **Red Flags/Urgency**: Clearly highlight any red flags or warning signs that would require immediate emergency care or urgent specialist referral.\n\n"
427
+ "Always maintain a professional, empathetic, and medically accurate tone. Present information clearly, using bullet points or numbered lists where appropriate. "
428
+ "Crucially, always conclude your response by strongly advising the user to consult a qualified healthcare professional for a definitive diagnosis and personalized treatment plan, "
429
+ "as this AI is for informational purposes only and not a substitute for professional medical advice."
430
+ )
431
+ try:
432
+ chat_completion = groq_client.chat.completions.create(
433
+ messages=[
434
+ {"role": "system", "content": system_prompt},
435
+ {"role": "user", "content": user_query},
436
+ ],
437
+ model=model,
438
+ temperature=0.7,
439
+ max_tokens=800, # Increased max_tokens to allow for more detailed responses
440
+ top_p=1,
441
+ stop=None,
442
+ stream=False,
443
+ )
444
+ return chat_completion.choices[0].message.content
445
+ except Exception as e:
446
+ st.error(f"Groq API call failed: {e}")
447
+ return "Sorry, I am unable to process that request at the moment due to an AI service error."
448
+
449
+ # --- Clinical Diagnosis Model Integration (DATEXIS/CORe-clinical-diagnosis-prediction) ---
450
+ def get_diagnosis_and_severity_from_model(symptoms_text):
451
+ """
452
+ Function to get diagnosis predictions and infer severity from DATEXIS/CORe-clinical-diagnosis-prediction
453
+ using Hugging Face Inference API.
454
+ """
455
+ if not HF_MODEL_AVAILABLE or not HF_API_TOKEN:
456
+ st.warning("Hugging Face API key not available. Cannot assess severity.")
457
+ return "Severity Assessment Unavailable (API Key Missing)", []
458
+
459
+ API_URL = "https://api-inference.huggingface.co/models/DATEXIS/CORe-clinical-diagnosis-prediction"
460
+ headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
461
+ payload = {"inputs": symptoms_text}
462
+ try:
463
+ response = requests.post(API_URL, headers=headers, json=payload)
464
+ response.raise_for_status()
465
+ result = response.json()
466
+
467
+ raw_predicted_diagnoses = []
468
+ threshold = 0.5
469
+ if result and isinstance(result, list) and len(result) > 0 and isinstance(result[0], list):
470
+ for prediction_set in result:
471
+ for item in prediction_set:
472
+ if item['score'] >= threshold:
473
+ raw_predicted_diagnoses.append(item['label'])
474
+
475
+ filtered_diagnoses = []
476
+ generic_filter_keywords = [
477
+ 'unspecified', 'acute', 'chronic', 'use', 'status', 'other', 'not elsewhere classified',
478
+ 'no diagnosis', 'history of', 'finding', 'problem', 'syndrome', 'disease',
479
+ 'disorder', 'condition', 'code', 'category', 'episode', 'complication',
480
+ 'sequelae', 'factor', 'manifestation', 'procedure', 'examination', 'observation',
481
+ 'symptoms', 'sign', 'unconfirmed', 'type', 'group', 'normal', 'unknown', 'level',
482
+ 'positive', 'negative', 'patient', 'value', 'test', 'result', 'diagnosis',
483
+ 'kidney', 'stage', 'without', 'essential',
484
+ 'organ', 'function', 'system', 'body', 'region', 'with', 'due to', 'related to',
485
+ 'clinical', 'consideration', 'presence', 'absence', 'mild', 'moderate', 'severe',
486
+ 'manifesting', 'affecting', 'affect', 'area', 'part', 'general', 'specific',
487
+ 'diagnosis of', 'history of', 'finding of', 'problem of', 'type of', 'group of',
488
+ 'unlikely', 'possible', 'likely',
489
+ 'symptom', 'sign', 'pain', 'fever', 'cough', 'vomiting', 'nausea', 'rash', 'headache', 'fatigue', 'diarrhea', 'sore throat', 'hemorrhage'
490
+ ]
491
+
492
+ for diagnosis_label in raw_predicted_diagnoses:
493
+ lower_diag = diagnosis_label.lower()
494
+ is_generic = False
495
+ for generic_kw in generic_filter_keywords:
496
+ if re.search(r'\b' + re.escape(generic_kw) + r'\b', lower_diag) and len(lower_diag.split()) <= 2:
497
+ is_generic = True
498
+ break
499
+ if lower_diag.replace('.', '').isdigit() and len(lower_diag.replace('.', '')) < 5:
500
+ is_generic = True
501
+ if len(lower_diag.split()) <= 2 and lower_diag in ['pain', 'fever', 'cough', 'vomiting', 'nausea', 'rash', 'headache', 'fatigue', 'diarrhea', 'sore throat', 'hemorrhage']:
502
+ is_generic = True
503
+
504
+ if not is_generic:
505
+ filtered_diagnoses.append(diagnosis_label)
506
+
507
+ filtered_diagnoses = list(dict.fromkeys(filtered_diagnoses))
508
+ if len(filtered_diagnoses) > 5:
509
+ filtered_diagnoses = filtered_diagnoses[:5]
510
+
511
+ if not filtered_diagnoses:
512
+ return "Overall Symptom Severity (AI Assessment): Undetermined Severity", []
513
+
514
+ severity_map_keywords = {
515
+ "High Severity": ['heart attack', 'stroke', 'failure', 'hemorrhage', 'cancer', 'acute respiratory', 'sepsis', 'cardiac arrest', 'severe', 'malignant', 'emergency', 'rupture', 'infarction', 'coma', 'shock', 'decompensation', 'crisis', 'perforation', 'ischemia', 'embolism', 'aneurysm', 'critical'],
516
+ "Moderate Severity": ['hypertension', 'diabetes', 'pneumonia', 'infection', 'chronic', 'inflammation', 'moderate', 'insufficiency', 'fracture', 'ulcer', 'hepatitis', 'renal', 'vascular', 'disease', 'disorder', 'syndrome', 'acute', 'bronchitis', 'appendicitis', 'gallstones', 'pancreatitis', 'lupus', 'arthritis'],
517
+ "Low Severity": ['headache', 'pain', 'mild', 'allergy', 'fever', 'cough', 'common cold', 'dermatitis', 'arthritis', 'influenza', 'viral', 'sprain', 'strain', 'gastritis', 'sore throat', 'conjunctivitis', 'sinusitis', 'bruise', 'rash', 'minor']
518
+ }
519
+
520
+ overall_severity = "Low Severity"
521
+ for diagnosis_label in filtered_diagnoses:
522
+ diag_lower = diagnosis_label.lower()
523
+ if any(keyword in diag_lower for keyword in severity_map_keywords["High Severity"]):
524
+ return "Overall Symptom Severity (AI Assessment): High Severity", filtered_diagnoses
525
+ if any(keyword in diag_lower for keyword in severity_map_keywords["Moderate Severity"]):
526
+ if overall_severity == "Low Severity":
527
+ overall_severity = "Moderate Severity"
528
+ # Keep looping to see if a High Severity diagnosis is found later in the filtered list
529
+ # Don't return here if a Moderate is found, continue to check for High
530
+
531
+ return f"Overall Symptom Severity (AI Assessment): {overall_severity}", filtered_diagnoses
532
+
533
+ except requests.exceptions.RequestException as e:
534
+ st.error(f"Network or API Error during clinical diagnosis model call: {e}. Check API key and internet connection.")
535
+ return "Severity Assessment Unavailable (Network Error)", []
536
+ except Exception as e:
537
+ st.error(f"Error processing clinical diagnosis model response: {e}. Unexpected data format or mapping issue.")
538
+ return "Severity Assessment Unavailable (Processing Error)", []
539
+
540
+ # --- Functions for PDF generation (retained and adjusted for DB history) ---
541
+ def generate_pdf_report(history_data):
542
+ buffer = io.BytesIO()
543
+ c = canvas.Canvas(buffer, pagesize=letter)
544
+ width, height = letter
545
+ c.setFont("Helvetica-Bold", 16)
546
+ c.drawString(50, height - 50, "MediBot Health Report")
547
+ c.setFont("Helvetica", 10)
548
+ c.drawString(50, height - 70, f"Report Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
549
+ y = height - 100
550
+
551
+ for entry in history_data:
552
+ # history_data comes as list of tuples: (history_id, symptoms, predicted_diseases, query_timestamp)
553
+ timestamp, symptoms_text, predicted_diseases = entry[3], entry[1], entry[2]
554
+
555
+ if y < 100:
556
+ c.showPage()
557
+ c.setFont("Helvetica-Bold", 16)
558
+ c.drawString(50, height - 50, "MediBot Health Report (Continued)")
559
+ c.setFont("Helvetica", 10)
560
+ y = height - 100
561
+
562
+ c.setFont("Helvetica-Bold", 10)
563
+ c.drawString(50, y, f"Timestamp: {timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
564
+ y -= 15
565
+ c.setFont("Helvetica", 10)
566
+
567
+ symptoms_line = f"Symptoms/Question: {symptoms_text}"
568
+ insight_line = f"Insight/Response: {predicted_diseases}"
569
+
570
+ textobject = c.beginText(50, y)
571
+ textobject.setFont("Helvetica", 10)
572
+
573
+ # Helper to wrap text within PDF
574
+ def draw_wrapped_text(text_obj, text, max_width):
575
+ nonlocal y
576
+ words = text.split(' ')
577
+ current_line_words = []
578
+ for word_idx, word in enumerate(words):
579
+ temp_line = ' '.join(current_line_words + [word])
580
+ if c.stringWidth(temp_line, "Helvetica", 10) < max_width:
581
+ current_line_words.append(word)
582
  else:
583
+ text_obj.textLine(' '.join(current_line_words))
584
+ y -= 12 # Line height
585
+ current_line_words = [word]
586
+ # Handle very long words that exceed max_width
587
+ if c.stringWidth(word, "Helvetica", 10) >= max_width:
588
+ # If a single word is too long, break it
589
+ chunk_size = 50 # Arbitrary chunk size for breaking long words
590
+ for i in range(0, len(word), chunk_size):
591
+ text_obj.textLine(word[i:i + chunk_size])
592
+ y -= 12
593
+ current_line_words = [] # Reset after a long word is broken
594
+ continue
595
+ if current_line_words: # Draw any remaining words
596
+ text_obj.textLine(' '.join(current_line_words))
597
+ y -= 12 # Line height
598
+
599
+ draw_wrapped_text(textobject, symptoms_line, width - 100)
600
+ draw_wrapped_text(textobject, insight_line, width - 100)
601
+ c.drawText(textobject)
602
+ y -= 20
603
+
604
+ c.save()
605
+ buffer.seek(0)
606
+ return buffer
607
+
608
+ # --- Symptom Checker Logic (Now using hardcoded data and severity) ---
609
+ def get_disease_info_from_csv(selected_symptoms: list, disease_symptoms_map, disease_description_map, disease_precaution_map, symptom_severity_map) -> list[tuple[str, str, list[str]]]:
610
+ """
611
+ Finds potential diseases, descriptions, and precautions based on selected symptoms
612
+ using the hardcoded CSV data, incorporating symptom severity.
613
+ Returns a list of tuples: (disease_name, description, [precautions])
614
+ """
615
+ matching_diseases_with_scores = {} # Stores disease -> weighted_score
616
+ # Normalize selected symptoms for consistent matching
617
+ normalized_selected_symptoms = [s.strip().lower() for s in selected_symptoms]
618
+
619
+ for disease, symptoms_list in disease_symptoms_map.items():
620
+ # Convert the symptoms_list from map to lowercase for comparison
621
+ normalized_disease_symptoms = [s.lower() for s in symptoms_list]
622
+ weighted_score = 0
623
+ for selected_symptom in normalized_selected_symptoms:
624
+ if selected_symptom in normalized_disease_symptoms:
625
+ # Add severity weight if available, otherwise a default of 1
626
+ # The .get() method is safe if a symptom is in dataset.csv but not in Symptom-severity.csv
627
+ weight = symptom_severity_map.get(selected_symptom, 1)
628
+ weighted_score += weight
629
+ if weighted_score > 0:
630
+ matching_diseases_with_scores[disease] = weighted_score
631
+
632
+ # Sort diseases by the weighted score (highest first)
633
+ sorted_matching_diseases = sorted(matching_diseases_with_scores.items(), key=lambda item: item[1], reverse=True)
634
+
635
+ results = []
636
+ # Limit to top 5 most matching diseases (or fewer if less than 5 matches)
637
+ for disease_name, _ in sorted_matching_diseases[:5]:
638
+ description = disease_description_map.get(disease_name, "No description available.")
639
+ precautions = disease_precaution_map.get(disease_name, ["No precautions available."])
640
+ results.append((disease_name, description, precautions))
641
+ return results
642
+
643
+ # --- Function to parse insight text for analytics ---
644
+ def parse_insight_text_for_conditions(insight_text: str) -> list[str]:
645
+ """
646
+ Parses the combined insight text from history to extract condition names.
647
+ Expected format: "Severity. Dataset Conditions: Cond1, Cond2; AI Suggestions: AICond1, AIC2"
648
+ or just "Severity. No specific insights found."
649
+ """
650
+ conditions = []
651
+ # Regex to find "Dataset Conditions: ..." and "AI Suggestions: ..." parts
652
+ dataset_match = re.search(r"Dataset Conditions:\s*([^;]+)", insight_text)
653
+ ai_match = re.search(r"AI Suggestions:\s*(.+)", insight_text)
654
+
655
+ if dataset_match:
656
+ dataset_str = dataset_match.group(1).strip()
657
+ if dataset_str and dataset_str.lower() != "no specific insights found":
658
+ conditions.extend([c.strip() for c in dataset_str.split(',') if c.strip()])
659
+
660
+ if ai_match:
661
+ ai_str = ai_match.group(1).strip()
662
+ if ai_str and ai_str.lower() != "no specific insights found" and ai_str.lower() != "ai assistant is not available.":
663
+ conditions.extend([c.strip() for c in ai_str.split(',') if c.strip()])
664
+
665
+ # Remove duplicates and return
666
+ return list(set(conditions))
667
+
668
+ # --- Main Application Logic ---
669
+ # Ensure feedback_input is initialized if not already (redundant if done at start, but harmless)
670
+ if "feedback_input" not in st.session_state:
671
+ st.session_state.feedback_input = ""
672
+
673
+
674
+ if st.session_state.show_welcome:
675
+ # A more visually appealing welcome page
676
+ st.markdown("<h1 style='color: #0056b3; font-size: 3.5em; margin-top: 50px; text-shadow: 2px 2px 4px rgba(0,0,0,0.1);'>MediBot - Your Personal Health Assistant 🏥</h1>", unsafe_allow_html=True)
677
+ st.markdown("""
678
+ <div style='background-color: #e6f7ff; padding: 30px; border-radius: 15px; text-align: center; margin-top: 30px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);'>
679
+ <p style='font-size: 1.2em; line-height: 1.8; color: #336699;'>
680
+ Explore possible causes and precautions for your symptoms and get answers to health-related questions using advanced AI.
681
+ Your health journey, simplified and supported.
682
+ </p>
683
+ <ul style='list-style-type: none; padding: 0; margin-top: 25px; display: inline-block; text-align: left;'>
684
+ <li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>✅ <b>Symptom Checker:</b> Get insights from a comprehensive symptom list.</li>
685
+ <li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>💬 <b>AI Chatbot:</b> Ask general health questions.</li>
686
+ <li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>👤 <b>User Management:</b> Save your health history.</li>
687
+ <li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>📜 <b>Persistent History:</b> View and download past records.</li>
688
+ <li style='font-size: 1.1em; margin-bottom: 10px; color: #2a527a;'>📈 <b>Usage Insights:</b> See trends in conditions.</li>
689
+ </ul>
690
+ </div>
691
+ """, unsafe_allow_html=True)
692
+ st.markdown("<div style='text-align: center; margin-top: 40px;'>", unsafe_allow_html=True)
693
+ if st.button("Get Started", key="welcome_button"):
694
+ st.session_state.show_welcome = False
695
+ st.rerun() # Force rerun to ensure app reloads to main page
696
+ st.markdown("</div>", unsafe_allow_html=True)
697
+ st.markdown("""
698
+ <div style='background-color: #fff8e1; padding: 20px; border-radius: 10px; margin-top: 50px; text-align: center; color: #8a6d3b; box-shadow: 0 4px 10px rgba(0,0,0,0.05);'>
699
+ <p style='font-size: 0.9em; margin-bottom: 0;'>
700
+ <b>Important Disclaimer:</b> This app provides preliminary health information based on symptoms and AI analysis.
701
+ <b>It is not a substitute for professional medical advice, diagnosis, or treatment.</b> Always consult a qualified healthcare provider for a definitive diagnosis and personalized treatment plan, as this AI is for informational purposes only and not a substitute for professional medical advice.
702
+ </p>
703
+ </div>
704
+ """, unsafe_allow_html=True)
705
+ else:
706
+ # Displaying disclaimer at the top of the main app view
707
+ st.markdown("<div class='stContainer' style='background-color: #fff8e1; border: 1px solid #ffeeba;'>"
708
+ "<p style='margin-bottom: 0; font-size: 0.95em; color: #856404;'>"
709
+ "<strong>Disclaimer</strong>: This app provides preliminary health information based on symptoms and AI analysis. "
710
+ "It is not a substitute for professional medical advice, diagnosis, or treatment. Always consult a qualified healthcare provider."
711
+ "</p>"
712
+ "</div>", unsafe_allow_html=True)
713
+
714
+ # Sidebar for User Management (remains in sidebar)
715
+ with st.sidebar:
716
+ st.header("User Management")
717
+ show_user_management()
718
+ st.markdown("---")
719
+ st.header("Navigation")
720
+ # Add a button to clear chat history
721
+ if st.button("Clear Chat History", help="Clears all messages from the current session."):
722
+ st.session_state.chat_history = []
723
+ st.session_state.last_chat_response = ""
724
+ st.session_state.chat_input_value = ""
725
+ st.success("Chat history cleared!")
726
+ # PDF Download Button - available if user is logged in and has history
727
+ if st.session_state.get("logged_in_user"):
728
+ user_history = get_user_history(st.session_state["logged_in_user"])
729
+ if user_history:
730
+ pdf_buffer = generate_pdf_report(user_history)
731
+ st.download_button(
732
+ label="Download Health Report (PDF)",
733
+ data=pdf_buffer,
734
+ file_name="medibot_health_report.pdf",
735
+ mime="application/pdf",
736
+ help="Download your chat and symptom checker history as a PDF report."
737
+ )
738
+ else:
739
+ st.info("No history to download yet. Interact with MediBot to generate a report.")
740
+
741
+ st.markdown("<h1 style='text-align: center; color: #0056b3;'>MediBot - Your Health Assistant 🩺</h1>", unsafe_allow_html=True)
742
+
743
+ # Top-level tabs: Home, History, Feedback, About, Insights
744
+ # Re-ordered tabs slightly to put Insights closer to History
745
+ main_tab_home, main_tab_history, main_tab_insights, main_tab_feedback, main_tab_about = st.tabs(["Home", "History", "Insights", "Feedback", "About"])
746
+
747
+ with main_tab_home:
748
+ st.markdown("<h2>How can I help you today?</h2>", unsafe_allow_html=True)
749
+ # Nested tabs for Symptom Checker and Chat with MediBot
750
+ tab_symptom_checker, tab_chatbot = st.tabs(["Symptom Checker", "Chat with MediBot"])
751
+
752
+ with tab_symptom_checker:
753
+ st.markdown("<h3>Select from common symptoms to get insights:</h3>", unsafe_allow_html=True)
754
+ selected_symptoms = st.multiselect(
755
+ "Select your symptoms:",
756
+ options=hardcoded_symptoms, # Use symptoms derived from CSV
757
+ default=[],
758
+ key="symptom_select",
759
+ help="Choose one or more symptoms you are experiencing."
760
+ )
761
+
762
+ if st.button("Get Health Insight", key="diagnose_button"):
763
+ if not selected_symptoms:
764
+ st.error("Please select at least one symptom.")
765
  else:
766
+ with st.spinner("Analyzing symptoms..."):
767
+ # Get overall severity and predicted diagnoses from the Hugging Face model
768
+ combined_symptoms_text = ", ".join(selected_symptoms)
769
+ severity_assessment_text, predicted_diagnoses_from_model = get_diagnosis_and_severity_from_model(combined_symptoms_text)
770
+
771
+ # Get relevant disease info from your CSV datasets
772
+ diseases_from_csv = get_disease_info_from_csv(selected_symptoms, disease_symptoms_map, disease_description_map, disease_precaution_map, symptom_severity_map)
773
+
774
+ st.markdown(f"**{severity_assessment_text}**", unsafe_allow_html=True)
775
+ st.markdown("<h2>Possible Conditions and Insights:</h2>", unsafe_allow_html=True) # Unified Heading
776
+
777
+ # --- Process and display CSV-based diseases first ---
778
+ if diseases_from_csv:
779
+ st.markdown("<h4>From our Medical Knowledge Base:</h4>", unsafe_allow_html=True)
780
+ for disease_name, description, precautions_list in diseases_from_csv:
781
+ with st.expander(disease_name):
782
+ st.write(f"**Description**: {description}")
783
+ st.write("**Precautions**:")
784
+ if precautions_list:
785
+ for precaution in precautions_list:
786
+ if precaution: # Only display non-empty precautions
787
+ st.write(f"- {precaution}")
788
+ else:
789
+ st.write("- No specific precautions listed.")
790
+ st.markdown("---") # Separator for each disease in expander
791
  else:
792
+ st.info("No common conditions found for these symptoms in our dataset. Please try adding more specific symptoms or switch to 'Chat with MediBot' for a general query.")
793
+
794
+ # --- Integrate relevant AI Model diagnoses not covered by CSV ---
795
+ additional_ai_diagnoses = []
796
+ # Create a set of normalized disease names from CSV for quick lookup and fuzzy matching
797
+ csv_disease_names_normalized = {d[0].lower() for d in diseases_from_csv}
798
+
799
+ for model_diag in predicted_diagnoses_from_model:
800
+ model_diag_normalized = model_diag.lower()
801
+ is_covered_by_csv = False
802
+ # Check for strong fuzzy match with CSV diseases
803
+ for csv_diag_name in csv_disease_names_normalized:
804
+ # Use token_set_ratio for better matching of multi-word terms, less sensitive to word order
805
+ if fuzz.token_set_ratio(model_diag_normalized, csv_diag_name) > 85: # High threshold for a strong match
806
+ is_covered_by_csv = True
807
+ break
808
+ if not is_covered_by_csv:
809
+ additional_ai_diagnoses.append(model_diag)
810
+
811
+ if additional_ai_diagnoses:
812
+ st.markdown("<h4>Additional AI-Suggested Clinical Considerations:</h4>", unsafe_allow_html=True)
813
+ st.write("The AI model suggests the following clinical terms based on your symptoms:")
814
+ for diag in additional_ai_diagnoses:
815
+ st.write(f"- **{diag}**: This is a medical term that the AI found relevant. Please consult a healthcare professional for more details.")
816
+ st.info("These are general medical terms and require professional interpretation. They may not have specific descriptions or precautions available in our dataset.")
817
+ elif not diseases_from_csv: # Only if no CSV diseases AND no unique AI diagnoses
818
+ st.info("The AI model could not confidently predict specific or unique diagnoses from the provided symptoms. Try different symptoms or consult a doctor.")
819
+
820
+ st.write("---") # Final Separator
821
+
822
+ # --- Save to history (adjusting content to reflect unified response) ---
823
+ history_content_parts = []
824
+ if diseases_from_csv:
825
+ history_content_parts.append("Dataset Conditions: " + ", ".join([d[0] for d in diseases_from_csv]))
826
+ if additional_ai_diagnoses:
827
+ history_content_parts.append("AI Suggestions: " + ", ".join(additional_ai_diagnoses))
828
+
829
+ # Combine all insight for history
830
+ predicted_diseases_str_for_history = f"{severity_assessment_text.replace('Overall Symptom Severity (AI Assessment): ', '')}. " + "; ".join(history_content_parts)
831
+ if not history_content_parts:
832
+ predicted_diseases_str_for_history = f"{severity_assessment_text.replace('Overall Symptom Severity (AI Assessment): ', '')}. No specific insights found."
833
+
834
+ if st.session_state.get("user_id"):
835
+ # Corrected call: Removed the first argument (user_id)
836
+ save_history_if_logged_in(", ".join(selected_symptoms), predicted_diseases_str_for_history)
837
+ st.success("Analysis saved to your history.")
838
+ else:
839
+ st.info("Log in to save this analysis to your history.")
840
+
841
+ with tab_chatbot: # Ask a Health Question (Chatbot)
842
+ st.markdown("<h3>Describe your symptoms or ask a general health question:</h3>", unsafe_allow_html=True)
843
+ # Use st.form with clear_on_submit=True to clear the text area
844
+ with st.form("chat_form", clear_on_submit=True):
845
+ user_question = st.text_area(
846
+ "Input your issue:",
847
+ value=st.session_state.chat_input_value, # Use session state for value
848
+ key="chat_input_widget", # Unique widget key
849
+ placeholder="e.g., I am having a severe fever and body aches. Or, what are the causes of high blood pressure?",
850
+ height=120 # Slightly taller text area
851
+ )
852
+ chat_submit_button = st.form_submit_button("Ask MediBot")
853
+
854
+ if chat_submit_button:
855
+ if user_question:
856
+ # For the chatbot, if it's a direct question, severity is undetermined
857
+ # unless passed from a preceding symptom check.
858
+ # For simplicity, if not from symptom checker, pass "Undetermined Severity".
859
+ # You could enhance this to try to infer severity from the question itself if needed.
860
+ severity_for_groq_prompt = "Undetermined Severity"
861
+ # If you want to use the severity from a recent symptom check,
862
+ # you'd need to store it in session_state and pass it here.
863
+ # For now, we're keeping the Q&A separate.
864
+ with st.spinner("MediBot is thinking..."):
865
+ groq_answer = get_groq_response(user_question, severity_label=severity_for_groq_prompt)
866
+ st.markdown("**MediBot's Answer:**")
867
+ st.write(groq_answer)
868
+
869
+ # Save Q&A to chat history (if desired, not necessarily DB)
870
+ st.session_state.chat_history.append({"user": user_question, "bot": groq_answer})
871
+
872
+ # Save to database history if logged in
873
+ if st.session_state.get("user_id"):
874
+ # Corrected call: Removed the first argument (user_id)
875
+ save_history_if_logged_in(f"Question: {user_question}", groq_answer)
876
+ st.success("Your question and answer have been saved to history.")
877
+ else:
878
+ st.info("Log in to save this Q&A to your history.")
879
+ else:
880
+ st.warning("Please type your question to get an answer.")
881
+
882
+ with main_tab_history:
883
+ st.header("Your Health History")
884
+ user_id = st.session_state.get("user_id")
885
+ if user_id:
886
+ st.info("This section shows your saved symptom analyses and health questions.")
887
+ history_data = get_user_history(user_id) # Fetch history from database
888
+
889
+ if history_data:
890
+ st.subheader("Past Interactions:")
891
+ # Display history in reverse chronological order using expanders
892
+ for entry in reversed(history_data):
893
+ timestamp, symptoms_text, insight_text = entry[3], entry[1], entry[2]
894
+
895
+ # Determine summary title based on the nature of the input
896
+ summary_title_prefix = ""
897
+ expander_icon = "" # Initialize icon here
898
+ if symptoms_text.startswith("Question: "):
899
+ keyword = extract_keyword(symptoms_text, hardcoded_symptoms)
900
+ summary_title_prefix = f"Question: {keyword}" if keyword != "Unknown" else "General Question"
901
+ expander_icon = "💬"
902
+ else: # Symptom checker entry (🩺)
903
+ summary_conditions = parse_insight_text_for_conditions(insight_text)
904
+ if summary_conditions:
905
+ summary_title_prefix = ", ".join(summary_conditions[:3])
906
+ if len(summary_conditions) > 3:
907
+ summary_title_prefix += "..."
908
+ else:
909
+ # MODIFIED LOGIC: If no conditions identified, try to use input symptoms
910
+ input_symptoms_match = re.search(r"Symptoms/Question: (.*)", symptoms_text) # Adjusted regex for "Symptoms/Question:"
911
+ if input_symptoms_match:
912
+ extracted_input_symptoms = input_symptoms_match.group(1).strip()
913
+ # Clean up potential extra spaces/commas, take first few if too many
914
+ clean_symptoms = [s.strip() for s in extracted_input_symptoms.split(',') if s.strip()]
915
+ if clean_symptoms:
916
+ summary_title_prefix = ", ".join(clean_symptoms[:3])
917
+ if len(clean_symptoms) > 3:
918
+ summary_title_prefix += "..."
919
  else:
920
+ summary_title_prefix = "No conditions identified" # Fallback if no symptoms extracted
921
+ else:
922
+ summary_title_prefix = "No conditions identified" # Fallback if input format is unexpected
923
+ expander_icon = "🩺"
924
+
925
+ # MODIFIED LINE: Use Markdown heading (e.g., ###) for larger font and bolding
926
+ # Experiment with ## or ### to find the desired size.
927
+ expander_label = f"### **{timestamp.strftime('%Y-%m-%d %H:%M')}** - {expander_icon} *{summary_title_prefix}*"
928
+ with st.expander(expander_label):
929
+ st.write(f"**Your Input:** {symptoms_text}")
930
+ st.write(f"**MediBot's Insight:** {insight_text}")
931
+
932
+ # PDF Download button
933
+ pdf_buffer = generate_pdf_report(history_data)
934
+ st.download_button(
935
+ label="Download History as PDF",
936
+ data=pdf_buffer,
937
+ file_name="MediBot_Health_Report.pdf",
938
+ mime="application/pdf"
939
+ )
940
+ else:
941
+ st.info("No history found for your account. Start interacting with MediBot!")
942
+ else:
943
+ st.warning("Please log in to view your health history.")
944
+
945
+ with main_tab_insights:
946
+ st.header("Your Health Insights")
947
+ user_id = st.session_state.get("user_id")
948
+ if user_id:
949
+ st.info("This section provides insights into the conditions identified in your past interactions.")
950
+ history_data = get_user_history(user_id)
951
+ if history_data:
952
+ # Filter data for the last 30 days
953
+ thirty_days_ago = datetime.now() - timedelta(days=30)
954
+ recent_history = [
955
+ entry for entry in history_data
956
+ if entry[3] and entry[3] > thirty_days_ago # entry[3] is query_timestamp
957
+ ]
958
+
959
+ if recent_history:
960
+ all_conditions = []
961
+ for entry in recent_history:
962
+ # entry[2] is predicted_diseases string
963
+ conditions_from_entry = parse_insight_text_for_conditions(entry[2])
964
+ all_conditions.extend(conditions_from_entry)
965
+
966
+ if all_conditions:
967
+ # Count occurrences of each condition
968
+ condition_counts = pd.Series(all_conditions).value_counts().reset_index()
969
+ condition_counts.columns = ['Condition', 'Count']
970
+ st.subheader("Most Frequent Conditions in Last 30 Days:")
971
+ # Display as a bar chart
972
+ st.bar_chart(condition_counts, x='Condition', y='Count', use_container_width=True)
973
+ st.write("This chart shows how many times each condition (from both dataset and AI suggestions) appeared in your analyses over the past 30 days.")
974
+ else:
975
+ st.info("No specific conditions were identified in your recent history (last 30 days).")
976
+ else:
977
+ st.info("You have no health interactions in the last 30 days to generate insights.")
978
+ else:
979
+ st.info("No history found for your account. Start interacting with MediBot to see insights!")
980
+ else:
981
+ st.warning("Please log in to view your health insights.")
982
+
983
+ with main_tab_feedback:
984
+ st.header("Share Your Feedback")
985
+ st.write("We appreciate your feedback to improve MediBot.")
986
+ with st.form("feedback_form", clear_on_submit=True):
987
+ feedback_text = st.text_area("Your feedback:", height=150, key="feedback_text_area")
988
+ rating = st.slider("Rate your experience (1-5 stars):", 1, 5, 3, key="feedback_rating")
989
+ feedback_submit_button = st.form_submit_button("Submit Feedback")
990
+
991
+ if feedback_submit_button:
992
+ if feedback_text:
993
+ # In a real app, you would save this to a database
994
+ st.session_state.feedback.append({"text": feedback_text, "rating": rating, "timestamp": datetime.now()})
995
+ st.success("Thank you for your feedback! It has been submitted.")
996
+ st.session_state.feedback_input = "" # Clear after submission
997
+ else:
998
+ st.warning("Please enter your feedback before submitting.")
999
+
1000
+ with main_tab_about:
1001
+ st.header("About MediBot")
1002
+ st.write("""
1003
+ MediBot is a health assistant designed to provide general information based on your symptoms and answer health-related questions.
1004
+ It utilizes various AI models and medical datasets to offer insights.
1005
+
1006
+ **Technology Stack:**
1007
+ - **Streamlit**: For the interactive web application interface.
1008
+ - **Groq API**: Powers the conversational AI for health questions using high-performance language models (e.g., Llama-3), enabling rapid and relevant responses.
1009
+ - **Hugging Face Inference API**: Integrates with specialized medical text classification models, specifically `DATEXIS/CORe-clinical-diagnosis-prediction`, to predict possible clinical diagnoses (often mapping to ICD-9 codes) from symptom inputs.
1010
+ - **Pandas**: For efficient data handling and processing of local CSV medical datasets (symptoms, descriptions, precautions).
1011
+ - **ReportLab**: For generating downloadable PDF reports of your personalized health history.
1012
+
1013
+ **Datasets Used:**
1014
+ - Symptom-to-Disease mapping, Descriptions, and Precautions sourced from:
1015
+ "Disease Symptom Prediction" dataset available on Kaggle.
1016
+ [https://www.kaggle.com/datasets/itachi9604/disease-symptom-description-dataset](https://www.kaggle.com/datasets/itachi9604/disease-symptom-description-dataset)
1017
+ *Credit to Itachi9604 for compiling this valuable dataset.*
1018
+
1019
+ **Important Disclaimer**: This application is for informational purposes only and should not be used as a substitute for professional medical advice, diagnosis, or treatment. Always consult with a qualified healthcare provider for any health concerns or before making any decisions related to your health.
1020
+ """)
1021
+ st.markdown("[Learn more about Streamlit](https://streamlit.io/)")
1022
+ st.markdown("[Learn more about Groq](https://groq.com/)")
1023
+ st.markdown("[Learn more about Hugging Face](https://huggingface.co/)")
1024
+
1025
+ st.markdown("---")
1026
+ st.markdown("For professional medical advice, always consult a qualified healthcare provider.")