Spaces:
Sleeping
Sleeping
after changes
Browse files
app.py
CHANGED
@@ -2,7 +2,6 @@ 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
|
@@ -13,10 +12,49 @@ from groq import Groq
|
|
13 |
from reportlab.lib.pagesizes import letter
|
14 |
from reportlab.pdfgen import canvas
|
15 |
|
16 |
-
# Import database
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
# Load environment variables from .env.local for local development.
|
22 |
# On Hugging Face Spaces, st.secrets will be used.
|
@@ -33,10 +71,6 @@ try:
|
|
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)
|
@@ -78,7 +112,7 @@ if "feedback_input" not in st.session_state: # For clearing feedback text area
|
|
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 ---
|
@@ -90,6 +124,7 @@ def load_csv_data():
|
|
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 = {}
|
@@ -98,13 +133,17 @@ def load_csv_data():
|
|
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
|
@@ -137,7 +176,6 @@ st.markdown("""
|
|
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 */
|
@@ -148,7 +186,6 @@ st.markdown("""
|
|
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 */
|
@@ -191,7 +228,6 @@ st.markdown("""
|
|
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"],
|
@@ -224,17 +260,16 @@ st.markdown("""
|
|
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 */
|
@@ -243,7 +278,7 @@ st.markdown("""
|
|
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 |
}
|
@@ -252,17 +287,15 @@ st.markdown("""
|
|
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 */
|
@@ -286,10 +319,10 @@ st.markdown("""
|
|
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 {
|
@@ -341,7 +374,6 @@ st.markdown("""
|
|
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 */
|
@@ -356,7 +388,6 @@ st.markdown("""
|
|
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;
|
@@ -373,7 +404,6 @@ st.markdown("""
|
|
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 {
|
@@ -398,8 +428,7 @@ st.markdown("""
|
|
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"):
|
@@ -409,7 +438,6 @@ def get_groq_response(user_query, severity_label="Undetermined Severity", model=
|
|
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.
|
@@ -455,7 +483,6 @@ def get_diagnosis_and_severity_from_model(symptoms_text):
|
|
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}
|
@@ -463,7 +490,6 @@ def get_diagnosis_and_severity_from_model(symptoms_text):
|
|
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):
|
@@ -488,7 +514,6 @@ def get_diagnosis_and_severity_from_model(symptoms_text):
|
|
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
|
@@ -500,7 +525,6 @@ def get_diagnosis_and_severity_from_model(symptoms_text):
|
|
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 |
|
@@ -527,9 +551,7 @@ def get_diagnosis_and_severity_from_model(symptoms_text):
|
|
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)", []
|
@@ -547,11 +569,9 @@ def generate_pdf_report(history_data):
|
|
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)
|
@@ -563,7 +583,6 @@ def generate_pdf_report(history_data):
|
|
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 |
|
@@ -600,7 +619,6 @@ def generate_pdf_report(history_data):
|
|
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
|
@@ -670,7 +688,6 @@ def parse_insight_text_for_conditions(insight_text: str) -> list[str]:
|
|
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)
|
@@ -711,10 +728,12 @@ else:
|
|
711 |
"</p>"
|
712 |
"</div>", unsafe_allow_html=True)
|
713 |
|
714 |
-
# Sidebar for User Management
|
715 |
with st.sidebar:
|
716 |
st.header("User Management")
|
717 |
-
|
|
|
|
|
718 |
st.markdown("---")
|
719 |
st.header("Navigation")
|
720 |
# Add a button to clear chat history
|
@@ -723,9 +742,10 @@ else:
|
|
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("
|
728 |
-
user_history = get_user_history(st.session_state["
|
729 |
if user_history:
|
730 |
pdf_buffer = generate_pdf_report(user_history)
|
731 |
st.download_button(
|
@@ -737,9 +757,10 @@ else:
|
|
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"])
|
@@ -832,9 +853,8 @@ else:
|
|
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 |
-
#
|
836 |
-
|
837 |
-
save_history_if_logged_in(st.session_state.get("logged_in_user"), ", ".join(selected_symptoms), predicted_diseases_str_for_history)
|
838 |
st.success("Analysis saved to your history.")
|
839 |
else:
|
840 |
st.info("Log in to save this analysis to your history.")
|
@@ -857,23 +877,18 @@ else:
|
|
857 |
# For the chatbot, if it's a direct question, severity is undetermined
|
858 |
# unless passed from a preceding symptom check.
|
859 |
# For simplicity, if not from symptom checker, pass "Undetermined Severity".
|
860 |
-
# You could enhance this to try to infer severity from the question itself if needed.
|
861 |
severity_for_groq_prompt = "Undetermined Severity"
|
862 |
-
# If you want to use the severity from a recent symptom check,
|
863 |
-
# you'd need to store it in session_state and pass it here.
|
864 |
-
# For now, we're keeping the Q&A separate.
|
865 |
with st.spinner("MediBot is thinking..."):
|
866 |
groq_answer = get_groq_response(user_question, severity_label=severity_for_groq_prompt)
|
867 |
st.markdown("**MediBot's Answer:**")
|
868 |
st.write(groq_answer)
|
869 |
-
|
870 |
# Save Q&A to chat history (if desired, not necessarily DB)
|
871 |
st.session_state.chat_history.append({"user": user_question, "bot": groq_answer})
|
872 |
|
873 |
# Save to database history if logged in
|
874 |
if st.session_state.get("user_id"):
|
875 |
# Prepend "Question: " to the input for history tracking
|
876 |
-
|
877 |
st.success("Your question and answer have been saved to history.")
|
878 |
else:
|
879 |
st.info("Log in to save this Q&A to your history.")
|
@@ -886,13 +901,11 @@ else:
|
|
886 |
if user_id:
|
887 |
st.info("This section shows your saved symptom analyses and health questions.")
|
888 |
history_data = get_user_history(user_id) # Fetch history from database
|
889 |
-
|
890 |
if history_data:
|
891 |
st.subheader("Past Interactions:")
|
892 |
# Display history in reverse chronological order using expanders
|
893 |
for entry in reversed(history_data):
|
894 |
timestamp, symptoms_text, insight_text = entry[3], entry[1], entry[2]
|
895 |
-
|
896 |
# Determine summary title based on the nature of the input
|
897 |
summary_title_prefix = ""
|
898 |
expander_icon = "" # Initialize icon here
|
@@ -924,7 +937,6 @@ else:
|
|
924 |
expander_icon = "🩺"
|
925 |
|
926 |
# MODIFIED LINE: Use Markdown heading (e.g., ###) for larger font and bolding
|
927 |
-
# Experiment with ## or ### to find the desired size.
|
928 |
expander_label = f"### **{timestamp.strftime('%Y-%m-%d %H:%M')}** - {expander_icon} *{summary_title_prefix}*"
|
929 |
with st.expander(expander_label):
|
930 |
st.write(f"**Your Input:** {symptoms_text}")
|
@@ -956,7 +968,6 @@ else:
|
|
956 |
entry for entry in history_data
|
957 |
if entry[3] and entry[3] > thirty_days_ago # entry[3] is query_timestamp
|
958 |
]
|
959 |
-
|
960 |
if recent_history:
|
961 |
all_conditions = []
|
962 |
for entry in recent_history:
|
@@ -1024,4 +1035,4 @@ else:
|
|
1024 |
st.markdown("[Learn more about Hugging Face](https://huggingface.co/)")
|
1025 |
|
1026 |
st.markdown("---")
|
1027 |
-
st.markdown("For professional medical advice, always consult a qualified healthcare provider.")
|
|
|
2 |
import os
|
3 |
import re
|
4 |
from datetime import datetime, timedelta
|
|
|
5 |
import numpy as np
|
6 |
import pandas as pd
|
7 |
import requests
|
|
|
12 |
from reportlab.lib.pagesizes import letter
|
13 |
from reportlab.pdfgen import canvas
|
14 |
|
15 |
+
# Import database functions directly here.
|
16 |
+
# These functions should NOT import anything from app.py or user_management.py
|
17 |
+
# If you don't have database.py, you'll need to create it with these functions.
|
18 |
+
# For demonstration, I'll assume they exist.
|
19 |
+
try:
|
20 |
+
from database import get_user_history, register_user, user_exists, check_user_credentials, save_user_history
|
21 |
+
# Assuming get_user_history, register_user, user_exists, check_user_credentials, save_user_history are in database.py
|
22 |
+
except ImportError:
|
23 |
+
st.error("Could not import database functions. Please ensure database.py exists and contains required functions.")
|
24 |
+
# Define dummy functions to allow the app to run without a real database.py for now
|
25 |
+
def get_user_history(user_id):
|
26 |
+
st.warning("Database function 'get_user_history' not implemented. History will not be persistent.")
|
27 |
+
return []
|
28 |
+
def register_user(user_id, full_name, dob, email, password):
|
29 |
+
st.warning("Database function 'register_user' not implemented. User registration will not be persistent.")
|
30 |
+
return True # Simulate success
|
31 |
+
def user_exists(user_id):
|
32 |
+
st.warning("Database function 'user_exists' not implemented. User existence check will not work.")
|
33 |
+
return False # Simulate user not existing
|
34 |
+
def check_user_credentials(user_id, password):
|
35 |
+
st.warning("Database function 'check_user_credentials' not implemented. Login will not work.")
|
36 |
+
return False # Simulate login failure
|
37 |
+
def save_user_history(user_id, symptoms, predicted_diseases):
|
38 |
+
st.warning("Database function 'save_user_history' not implemented. History saving will not be persistent.")
|
39 |
+
pass
|
40 |
+
|
41 |
+
|
42 |
+
# Import user management functions from user_management.py
|
43 |
+
# These functions will now receive st.session_state as an argument
|
44 |
+
from user_management import render_user_management_sidebar, save_history_to_db_if_logged_in
|
45 |
+
|
46 |
+
# Import utility functions
|
47 |
+
try:
|
48 |
+
from utils import extract_keyword # Ensure utils.py has the extract_keyword function
|
49 |
+
except ImportError:
|
50 |
+
st.error("Could not import utility functions. Please ensure utils.py exists and contains required functions.")
|
51 |
+
def extract_keyword(text, keywords):
|
52 |
+
# Dummy implementation if utils.py is missing
|
53 |
+
for kw in keywords:
|
54 |
+
if kw.lower() in text.lower():
|
55 |
+
return kw
|
56 |
+
return "Unknown"
|
57 |
+
|
58 |
|
59 |
# Load environment variables from .env.local for local development.
|
60 |
# On Hugging Face Spaces, st.secrets will be used.
|
|
|
71 |
GROQ_API_KEY = st.secrets.get("GROQ_API_KEY")
|
72 |
if not GROQ_API_KEY:
|
73 |
# Fallback to environment variable for local development if not in secrets
|
|
|
|
|
|
|
|
|
74 |
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
|
75 |
if GROQ_API_KEY:
|
76 |
groq_client = Groq(api_key=GROQ_API_KEY)
|
|
|
112 |
# Ensure user_id is initialized for saving history
|
113 |
if "user_id" not in st.session_state:
|
114 |
st.session_state.user_id = None
|
115 |
+
if "logged_in_user" not in st.session_state: # This will hold the user_id after successful login
|
116 |
st.session_state.logged_in_user = None
|
117 |
|
118 |
# --- HARDCODED DATA LOADING FROM CSVs ---
|
|
|
124 |
description_df = pd.read_csv('symptom_Description.csv').fillna('')
|
125 |
precaution_df = pd.read_csv('symptom_precaution.csv').fillna('')
|
126 |
severity_df = pd.read_csv('Symptom-severity.csv').fillna('') # Load symptom severity
|
127 |
+
|
128 |
# Prepare data for quick lookup
|
129 |
# Dataset mapping diseases to their symptoms
|
130 |
disease_symptoms_map = {}
|
|
|
133 |
# Get all symptoms for this disease, filtering out empty strings and 'Disease' column itself
|
134 |
symptoms = [s.strip().replace('_', ' ') for s in row.values[1:] if s.strip()]
|
135 |
disease_symptoms_map[disease] = symptoms
|
136 |
+
|
137 |
# Disease descriptions map
|
138 |
disease_description_map = {row['Disease']: row['Description'] for index, row in description_df.iterrows()}
|
139 |
+
|
140 |
# Disease precautions map
|
141 |
disease_precaution_map = {row['Disease']: [p.strip() for p in row.values[1:] if p.strip()] for index, row in precaution_df.iterrows()}
|
142 |
+
|
143 |
# Symptom severity map
|
144 |
# Ensure symptom names are consistent (e.g., lowercase and spaces instead of underscores)
|
145 |
symptom_severity_map = {row['Symptom'].strip().replace('_', ' ').lower(): row['weight'] for index, row in severity_df.iterrows()}
|
146 |
+
|
147 |
# Extract all unique symptoms for the multiselect
|
148 |
all_unique_symptoms = sorted(list(set(symptom for symptoms_list in disease_symptoms_map.values() for symptom in symptoms_list)))
|
149 |
return disease_symptoms_map, disease_description_map, disease_precaution_map, all_unique_symptoms, symptom_severity_map
|
|
|
176 |
margin-bottom: 2rem;
|
177 |
background-color: #ffffff; /* White background */
|
178 |
}
|
|
|
179 |
/* Headers */
|
180 |
h1, h2, h3, h4, h5, h6 {
|
181 |
color: #004d99; /* A professional darker blue */
|
|
|
186 |
h1 { font-size: 2.8em; text-align: center; color: #003366; } /* Larger, darker blue for main title */
|
187 |
h2 { font-size: 2.2em; border-bottom: 2px solid #e0e7ee; padding-bottom: 0.5em; margin-bottom: 1em; } /* Subtle line under h2 */
|
188 |
h3 { font-size: 1.6em; }
|
|
|
189 |
/* Main Tabs Styling (Home, History, Feedback, About, Insights) */
|
190 |
.stTabs [data-baseweb="tab-list"] {
|
191 |
gap: 20px; /* More space between tabs */
|
|
|
228 |
outline: none !important;
|
229 |
box-shadow: 0 0 0 0.25rem rgba(0, 123, 255, 0.25) !important; /* Subtle focus glow if needed */
|
230 |
}
|
|
|
231 |
/* Nested Tabs Styling (Symptom Checker, Chat with MediBot) */
|
232 |
/* This targets tabs that are children of other tabs, making them smaller */
|
233 |
.stTabs [data-baseweb="tab-list"]:has(.stTabs [data-baseweb="tab-list"]) [data-baseweb="tab"],
|
|
|
260 |
box-shadow: 0 2px 8px rgba(0,0,0,0.15); /* Subtle shadow for selected sub-tab */
|
261 |
outline: none !important; /* Ensure no outline when selected */
|
262 |
}
|
|
|
263 |
/* General Button Styling */
|
264 |
.stButton>button {
|
265 |
background-color: #007bff; /* Primary blue button */
|
266 |
color: white;
|
267 |
+
border-radius: 10px; /* More rounded */
|
268 |
padding: 12px 28px; /* More padding */
|
269 |
font-weight: 600; /* Semi-bold */
|
270 |
border: none;
|
271 |
cursor: pointer;
|
272 |
+
box-shadow: 0 5px 15px rgba(0,0,0,0.1); /* Softer, more diffuse shadow */
|
273 |
transition: all 0.3s ease-in-out; /* Smooth transitions */
|
274 |
font-size: 1.05em; /* Slightly larger text */
|
275 |
outline: none; /* Remove outline on focus */
|
|
|
278 |
}
|
279 |
.stButton>button:hover {
|
280 |
background-color: #0056b3; /* Darker blue on hover */
|
281 |
+
box-shadow: 0 8px 20px rgba(0,0,0,0.15); /* Larger shadow on hover */
|
282 |
transform: translateY(-3px); /* More pronounced lift */
|
283 |
outline: none; /* Ensure no outline on hover either */
|
284 |
}
|
|
|
287 |
width: 100%; /* Full width buttons in sidebar */
|
288 |
margin-bottom: 10px;
|
289 |
}
|
|
|
|
|
290 |
/* Input Fields, Selects, etc. */
|
291 |
.stTextInput>div>div>input,
|
292 |
.stTextArea>div>div>textarea, /* Target textarea as well */
|
293 |
.stDateInput>div>div>input,
|
294 |
.stMultiSelect>div>div {
|
295 |
+
border-radius: 10px; /* More rounded */
|
296 |
border: 1px solid #ced4da; /* Light grey border */
|
297 |
padding: 10px 15px; /* More padding */
|
298 |
+
box-shadow: inset 0 1px 4px rgba(0,0,0,0.05); /* Inner shadow */
|
299 |
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
300 |
background-color: #fefefe; /* Slightly off-white background */
|
301 |
outline: none; /* Remove default outline */
|
|
|
319 |
/* Expander (for disease info) */
|
320 |
.stExpander {
|
321 |
border: 1px solid #e0e7ee; /* Lighter grey border */
|
322 |
+
border-radius: 12px; /* More rounded */
|
323 |
margin-bottom: 15px; /* More space below */
|
324 |
background-color: #ffffff; /* White background */
|
325 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.05); /* Softer shadow */
|
326 |
overflow: hidden; /* Ensure rounded corners clip content */
|
327 |
}
|
328 |
.stExpander > div > div > .streamlit-expanderHeader {
|
|
|
374 |
.stAlert.st-error { background-color: #f8d7da; color: #721c24; border-color: #f5c6cb; }
|
375 |
/* Spinners */
|
376 |
.stSpinner { color: #007bff; } /* Spinner color matching primary button */
|
|
|
377 |
/* Disclaimer Box - using a more reliable target */
|
378 |
div[data-testid="stVerticalBlock"] > div:first-child > div:has(p > strong:contains("Disclaimer")) {
|
379 |
background-color: #fff8e1; /* Light yellow for warnings/disclaimers */
|
|
|
388 |
.stAlert[data-testid="stAlert"] p strong:contains("Important Disclaimer") {
|
389 |
color: #664d03;
|
390 |
}
|
|
|
391 |
/* General text improvements */
|
392 |
p {
|
393 |
line-height: 1.7;
|
|
|
404 |
.stExpander > div > div > .streamlit-expanderHeader {
|
405 |
font-size: 1.2em; /* Increased font size */
|
406 |
}
|
|
|
407 |
/* Media queries for specific mobile adjustments if needed (example) */
|
408 |
@media (max-width: 768px) {
|
409 |
h1 {
|
|
|
428 |
padding: 3px 8px !important;
|
429 |
}
|
430 |
}
|
431 |
+
</style>""", unsafe_allow_html=True)
|
|
|
432 |
|
433 |
# --- Groq API Response Function (updated system prompt) ---
|
434 |
def get_groq_response(user_query, severity_label="Undetermined Severity", model="llama-3.3-70b-versatile"):
|
|
|
438 |
"""
|
439 |
if not GROQ_AVAILABLE or groq_client is None:
|
440 |
return "AI assistant is not available."
|
|
|
441 |
# Augment the system prompt with severity information from clinical diagnosis model
|
442 |
# Note: 'severity_label' comes from the symptom checker. If asking a direct question,
|
443 |
# it might default to "Undetermined Severity" unless explicitly passed from a prior analysis.
|
|
|
483 |
if not HF_MODEL_AVAILABLE or not HF_API_TOKEN:
|
484 |
st.warning("Hugging Face API key not available. Cannot assess severity.")
|
485 |
return "Severity Assessment Unavailable (API Key Missing)", []
|
|
|
486 |
API_URL = "https://api-inference.huggingface.co/models/DATEXIS/CORe-clinical-diagnosis-prediction"
|
487 |
headers = {"Authorization": f"Bearer {HF_API_TOKEN}"}
|
488 |
payload = {"inputs": symptoms_text}
|
|
|
490 |
response = requests.post(API_URL, headers=headers, json=payload)
|
491 |
response.raise_for_status()
|
492 |
result = response.json()
|
|
|
493 |
raw_predicted_diagnoses = []
|
494 |
threshold = 0.5
|
495 |
if result and isinstance(result, list) and len(result) > 0 and isinstance(result[0], list):
|
|
|
514 |
'unlikely', 'possible', 'likely',
|
515 |
'symptom', 'sign', 'pain', 'fever', 'cough', 'vomiting', 'nausea', 'rash', 'headache', 'fatigue', 'diarrhea', 'sore throat', 'hemorrhage'
|
516 |
]
|
|
|
517 |
for diagnosis_label in raw_predicted_diagnoses:
|
518 |
lower_diag = diagnosis_label.lower()
|
519 |
is_generic = False
|
|
|
525 |
is_generic = True
|
526 |
if len(lower_diag.split()) <= 2 and lower_diag in ['pain', 'fever', 'cough', 'vomiting', 'nausea', 'rash', 'headache', 'fatigue', 'diarrhea', 'sore throat', 'hemorrhage']:
|
527 |
is_generic = True
|
|
|
528 |
if not is_generic:
|
529 |
filtered_diagnoses.append(diagnosis_label)
|
530 |
|
|
|
551 |
overall_severity = "Moderate Severity"
|
552 |
# Keep looping to see if a High Severity diagnosis is found later in the filtered list
|
553 |
# Don't return here if a Moderate is found, continue to check for High
|
|
|
554 |
return f"Overall Symptom Severity (AI Assessment): {overall_severity}", filtered_diagnoses
|
|
|
555 |
except requests.exceptions.RequestException as e:
|
556 |
st.error(f"Network or API Error during clinical diagnosis model call: {e}. Check API key and internet connection.")
|
557 |
return "Severity Assessment Unavailable (Network Error)", []
|
|
|
569 |
c.setFont("Helvetica", 10)
|
570 |
c.drawString(50, height - 70, f"Report Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
571 |
y = height - 100
|
|
|
572 |
for entry in history_data:
|
573 |
# history_data comes as list of tuples: (history_id, symptoms, predicted_diseases, query_timestamp)
|
574 |
timestamp, symptoms_text, predicted_diseases = entry[3], entry[1], entry[2]
|
|
|
575 |
if y < 100:
|
576 |
c.showPage()
|
577 |
c.setFont("Helvetica-Bold", 16)
|
|
|
583 |
c.drawString(50, y, f"Timestamp: {timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
|
584 |
y -= 15
|
585 |
c.setFont("Helvetica", 10)
|
|
|
586 |
symptoms_line = f"Symptoms/Question: {symptoms_text}"
|
587 |
insight_line = f"Insight/Response: {predicted_diseases}"
|
588 |
|
|
|
619 |
draw_wrapped_text(textobject, insight_line, width - 100)
|
620 |
c.drawText(textobject)
|
621 |
y -= 20
|
|
|
622 |
c.save()
|
623 |
buffer.seek(0)
|
624 |
return buffer
|
|
|
688 |
if "feedback_input" not in st.session_state:
|
689 |
st.session_state.feedback_input = ""
|
690 |
|
|
|
691 |
if st.session_state.show_welcome:
|
692 |
# A more visually appealing welcome page
|
693 |
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)
|
|
|
728 |
"</p>"
|
729 |
"</div>", unsafe_allow_html=True)
|
730 |
|
731 |
+
# Sidebar for User Management
|
732 |
with st.sidebar:
|
733 |
st.header("User Management")
|
734 |
+
# Call the refactored user management function, passing st.session_state
|
735 |
+
render_user_management_sidebar(st.session_state)
|
736 |
+
|
737 |
st.markdown("---")
|
738 |
st.header("Navigation")
|
739 |
# Add a button to clear chat history
|
|
|
742 |
st.session_state.last_chat_response = ""
|
743 |
st.session_state.chat_input_value = ""
|
744 |
st.success("Chat history cleared!")
|
745 |
+
|
746 |
# PDF Download Button - available if user is logged in and has history
|
747 |
+
if st.session_state.get("user_id"):
|
748 |
+
user_history = get_user_history(st.session_state["user_id"]) # Directly call get_user_history
|
749 |
if user_history:
|
750 |
pdf_buffer = generate_pdf_report(user_history)
|
751 |
st.download_button(
|
|
|
757 |
)
|
758 |
else:
|
759 |
st.info("No history to download yet. Interact with MediBot to generate a report.")
|
760 |
+
else:
|
761 |
+
st.info("Log in to download your health report.")
|
762 |
|
763 |
st.markdown("<h1 style='text-align: center; color: #0056b3;'>MediBot - Your Health Assistant 🩺</h1>", unsafe_allow_html=True)
|
|
|
764 |
# Top-level tabs: Home, History, Feedback, About, Insights
|
765 |
# Re-ordered tabs slightly to put Insights closer to History
|
766 |
main_tab_home, main_tab_history, main_tab_insights, main_tab_feedback, main_tab_about = st.tabs(["Home", "History", "Insights", "Feedback", "About"])
|
|
|
853 |
predicted_diseases_str_for_history = f"{severity_assessment_text.replace('Overall Symptom Severity (AI Assessment): ', '')}. No specific insights found."
|
854 |
|
855 |
if st.session_state.get("user_id"):
|
856 |
+
# Call the refactored save history function
|
857 |
+
save_history_to_db_if_logged_in(st.session_state.user_id, ", ".join(selected_symptoms), predicted_diseases_str_for_history)
|
|
|
858 |
st.success("Analysis saved to your history.")
|
859 |
else:
|
860 |
st.info("Log in to save this analysis to your history.")
|
|
|
877 |
# For the chatbot, if it's a direct question, severity is undetermined
|
878 |
# unless passed from a preceding symptom check.
|
879 |
# For simplicity, if not from symptom checker, pass "Undetermined Severity".
|
|
|
880 |
severity_for_groq_prompt = "Undetermined Severity"
|
|
|
|
|
|
|
881 |
with st.spinner("MediBot is thinking..."):
|
882 |
groq_answer = get_groq_response(user_question, severity_label=severity_for_groq_prompt)
|
883 |
st.markdown("**MediBot's Answer:**")
|
884 |
st.write(groq_answer)
|
|
|
885 |
# Save Q&A to chat history (if desired, not necessarily DB)
|
886 |
st.session_state.chat_history.append({"user": user_question, "bot": groq_answer})
|
887 |
|
888 |
# Save to database history if logged in
|
889 |
if st.session_state.get("user_id"):
|
890 |
# Prepend "Question: " to the input for history tracking
|
891 |
+
save_history_to_db_if_logged_in(st.session_state.user_id, f"Question: {user_question}", groq_answer)
|
892 |
st.success("Your question and answer have been saved to history.")
|
893 |
else:
|
894 |
st.info("Log in to save this Q&A to your history.")
|
|
|
901 |
if user_id:
|
902 |
st.info("This section shows your saved symptom analyses and health questions.")
|
903 |
history_data = get_user_history(user_id) # Fetch history from database
|
|
|
904 |
if history_data:
|
905 |
st.subheader("Past Interactions:")
|
906 |
# Display history in reverse chronological order using expanders
|
907 |
for entry in reversed(history_data):
|
908 |
timestamp, symptoms_text, insight_text = entry[3], entry[1], entry[2]
|
|
|
909 |
# Determine summary title based on the nature of the input
|
910 |
summary_title_prefix = ""
|
911 |
expander_icon = "" # Initialize icon here
|
|
|
937 |
expander_icon = "🩺"
|
938 |
|
939 |
# MODIFIED LINE: Use Markdown heading (e.g., ###) for larger font and bolding
|
|
|
940 |
expander_label = f"### **{timestamp.strftime('%Y-%m-%d %H:%M')}** - {expander_icon} *{summary_title_prefix}*"
|
941 |
with st.expander(expander_label):
|
942 |
st.write(f"**Your Input:** {symptoms_text}")
|
|
|
968 |
entry for entry in history_data
|
969 |
if entry[3] and entry[3] > thirty_days_ago # entry[3] is query_timestamp
|
970 |
]
|
|
|
971 |
if recent_history:
|
972 |
all_conditions = []
|
973 |
for entry in recent_history:
|
|
|
1035 |
st.markdown("[Learn more about Hugging Face](https://huggingface.co/)")
|
1036 |
|
1037 |
st.markdown("---")
|
1038 |
+
st.markdown("For professional medical advice, always consult a qualified healthcare provider.")
|