Update src/streamlit_app.py
Browse files- src/streamlit_app.py +114 -99
src/streamlit_app.py
CHANGED
|
@@ -38,7 +38,7 @@ model = genai.GenerativeModel('gemini-2.5-pro')
|
|
| 38 |
# 1. DATABASE SYSTEM
|
| 39 |
# -----------------------------------------------------------------------------
|
| 40 |
def init_db():
|
| 41 |
-
conn = sqlite3.connect('
|
| 42 |
c = conn.cursor()
|
| 43 |
c.execute('''CREATE TABLE IF NOT EXISTS users (email TEXT PRIMARY KEY, password TEXT, joined_date TEXT)''')
|
| 44 |
c.execute('''CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT, type TEXT, summary TEXT, risk_level TEXT, date TEXT)''')
|
|
@@ -48,7 +48,7 @@ def init_db():
|
|
| 48 |
def hash_pass(password): return hashlib.sha256(str.encode(password)).hexdigest()
|
| 49 |
|
| 50 |
def register_user(email, password):
|
| 51 |
-
conn = sqlite3.connect('
|
| 52 |
try:
|
| 53 |
conn.execute("INSERT INTO users VALUES (?, ?, ?)", (email, hash_pass(password), datetime.now().strftime("%Y-%m-%d")))
|
| 54 |
conn.commit()
|
|
@@ -57,20 +57,20 @@ def register_user(email, password):
|
|
| 57 |
finally: conn.close()
|
| 58 |
|
| 59 |
def login_user(email, password):
|
| 60 |
-
conn = sqlite3.connect('
|
| 61 |
data = conn.execute("SELECT * FROM users WHERE email=? AND password=?", (email, hash_pass(password))).fetchall()
|
| 62 |
conn.close()
|
| 63 |
return data
|
| 64 |
|
| 65 |
def add_history(email, type, summary, risk):
|
| 66 |
-
conn = sqlite3.connect('
|
| 67 |
conn.execute("INSERT INTO history (email, type, summary, risk_level, date) VALUES (?, ?, ?, ?, ?)",
|
| 68 |
(email, type, summary[:100], risk, datetime.now().strftime("%Y-%m-%d")))
|
| 69 |
conn.commit()
|
| 70 |
conn.close()
|
| 71 |
|
| 72 |
def get_user_stats(email):
|
| 73 |
-
conn = sqlite3.connect('
|
| 74 |
history = conn.execute("SELECT type, date FROM history WHERE email=? ORDER BY id DESC LIMIT 10", (email,)).fetchall()
|
| 75 |
last_scan = history[0][1] if history else "New User"
|
| 76 |
conn.close()
|
|
@@ -99,16 +99,15 @@ st.markdown("""
|
|
| 99 |
color: #f8fafc;
|
| 100 |
}
|
| 101 |
|
| 102 |
-
|
|
|
|
| 103 |
background: rgba(255, 255, 255, 0.03);
|
| 104 |
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 105 |
border-radius: 16px;
|
| 106 |
-
padding:
|
| 107 |
-
text-align: center;
|
| 108 |
-
backdrop-filter: blur(10px);
|
| 109 |
-
margin-bottom: 20px;
|
| 110 |
}
|
| 111 |
|
|
|
|
| 112 |
.result-card {
|
| 113 |
background: #0f172a;
|
| 114 |
border-radius: 12px;
|
|
@@ -227,88 +226,102 @@ def show_landing():
|
|
| 227 |
</div>
|
| 228 |
""", unsafe_allow_html=True)
|
| 229 |
|
|
|
|
| 230 |
st.markdown("<br><br><br><br>", unsafe_allow_html=True)
|
| 231 |
-
st.
|
| 232 |
-
|
| 233 |
-
<b>Disclaimer:</b> Medivio is an AI-powered analysis tool. It does not provide medical diagnosis.
|
| 234 |
-
Always consult a qualified healthcare professional for medical advice.
|
| 235 |
-
</div>
|
| 236 |
-
""", unsafe_allow_html=True)
|
| 237 |
|
| 238 |
-
# --- ABOUT (
|
| 239 |
def show_about():
|
| 240 |
if st.button("← Back"): go_to('landing')
|
| 241 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 242 |
|
| 243 |
-
|
|
|
|
| 244 |
|
|
|
|
| 245 |
st.markdown("""
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 251 |
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
To democratize access to medical information. We believe everyone should be able to
|
| 257 |
-
understand their own health records instantly, without waiting days for an appointment.
|
| 258 |
-
</p>
|
| 259 |
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
Medivio leverages <b>Google Gemini 1.5 Pro</b>, a multimodal AI model capable of processing
|
| 263 |
-
vision (images) and text simultaneously. This allows for context-aware analysis that
|
| 264 |
-
mimics the reasoning of a medical professional.
|
| 265 |
-
</p>
|
| 266 |
-
</div>
|
| 267 |
-
""", unsafe_allow_html=True)
|
| 268 |
|
| 269 |
# --- AUTH ---
|
| 270 |
def show_auth():
|
| 271 |
c1, c2, c3 = st.columns([1,1,1])
|
| 272 |
with c2:
|
| 273 |
st.markdown("<br><br>", unsafe_allow_html=True)
|
| 274 |
-
st.markdown("<div class='feature-card' style='text-align:left; padding:40px;'>", unsafe_allow_html=True)
|
| 275 |
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
if st.button("Create Account", type="secondary"):
|
| 290 |
-
st.session_state.auth_mode = 'register'
|
| 291 |
-
st.rerun()
|
| 292 |
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
st.session_state.auth_mode = 'login'
|
| 303 |
st.rerun()
|
| 304 |
-
else: st.error("Email already used.")
|
| 305 |
-
|
| 306 |
-
st.markdown("<hr style='border-color:rgba(255,255,255,0.1)'>", unsafe_allow_html=True)
|
| 307 |
-
if st.button("Back to Login", type="secondary"):
|
| 308 |
-
st.session_state.auth_mode = 'login'
|
| 309 |
-
st.rerun()
|
| 310 |
-
|
| 311 |
-
st.markdown("</div>", unsafe_allow_html=True)
|
| 312 |
|
| 313 |
# --- DASHBOARD ---
|
| 314 |
def show_dashboard():
|
|
@@ -345,31 +358,32 @@ def show_dashboard():
|
|
| 345 |
st.title("Diagnostic Interface")
|
| 346 |
|
| 347 |
if st.session_state.analysis_result is None:
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
|
|
|
| 373 |
|
| 374 |
else:
|
| 375 |
res = st.session_state.analysis_result
|
|
@@ -384,16 +398,17 @@ def show_dashboard():
|
|
| 384 |
parts = res.split("|||")
|
| 385 |
if len(parts) >= 5:
|
| 386 |
title, obs, risks, severity, actions = parts[0], parts[1], parts[2], parts[3], parts[4]
|
|
|
|
| 387 |
sev_clean = severity.strip().lower()
|
| 388 |
bar_class = "risk-fill-low"
|
| 389 |
color = "#22c55e"
|
| 390 |
if "medium" in sev_clean: bar_class = "risk-fill-med"; color = "#eab308"
|
| 391 |
if "high" in sev_clean: bar_class = "risk-fill-high"; color = "#ef4444"
|
| 392 |
|
|
|
|
| 393 |
st.markdown(f"""
|
| 394 |
-
<div
|
| 395 |
-
<h4 style='margin:0; color:{color}'>SEVERITY
|
| 396 |
-
<div class='risk-bar' style='background:rgba(255,255,255,0.1)'><div class='{bar_class}'></div></div>
|
| 397 |
</div>
|
| 398 |
""", unsafe_allow_html=True)
|
| 399 |
|
|
|
|
| 38 |
# 1. DATABASE SYSTEM
|
| 39 |
# -----------------------------------------------------------------------------
|
| 40 |
def init_db():
|
| 41 |
+
conn = sqlite3.connect('medivio_v9.db')
|
| 42 |
c = conn.cursor()
|
| 43 |
c.execute('''CREATE TABLE IF NOT EXISTS users (email TEXT PRIMARY KEY, password TEXT, joined_date TEXT)''')
|
| 44 |
c.execute('''CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT, type TEXT, summary TEXT, risk_level TEXT, date TEXT)''')
|
|
|
|
| 48 |
def hash_pass(password): return hashlib.sha256(str.encode(password)).hexdigest()
|
| 49 |
|
| 50 |
def register_user(email, password):
|
| 51 |
+
conn = sqlite3.connect('medivio_v9.db')
|
| 52 |
try:
|
| 53 |
conn.execute("INSERT INTO users VALUES (?, ?, ?)", (email, hash_pass(password), datetime.now().strftime("%Y-%m-%d")))
|
| 54 |
conn.commit()
|
|
|
|
| 57 |
finally: conn.close()
|
| 58 |
|
| 59 |
def login_user(email, password):
|
| 60 |
+
conn = sqlite3.connect('medivio_v9.db')
|
| 61 |
data = conn.execute("SELECT * FROM users WHERE email=? AND password=?", (email, hash_pass(password))).fetchall()
|
| 62 |
conn.close()
|
| 63 |
return data
|
| 64 |
|
| 65 |
def add_history(email, type, summary, risk):
|
| 66 |
+
conn = sqlite3.connect('medivio_v9.db')
|
| 67 |
conn.execute("INSERT INTO history (email, type, summary, risk_level, date) VALUES (?, ?, ?, ?, ?)",
|
| 68 |
(email, type, summary[:100], risk, datetime.now().strftime("%Y-%m-%d")))
|
| 69 |
conn.commit()
|
| 70 |
conn.close()
|
| 71 |
|
| 72 |
def get_user_stats(email):
|
| 73 |
+
conn = sqlite3.connect('medivio_v9.db')
|
| 74 |
history = conn.execute("SELECT type, date FROM history WHERE email=? ORDER BY id DESC LIMIT 10", (email,)).fetchall()
|
| 75 |
last_scan = history[0][1] if history else "New User"
|
| 76 |
conn.close()
|
|
|
|
| 99 |
color: #f8fafc;
|
| 100 |
}
|
| 101 |
|
| 102 |
+
/* Clean Card Style */
|
| 103 |
+
.stContainer {
|
| 104 |
background: rgba(255, 255, 255, 0.03);
|
| 105 |
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 106 |
border-radius: 16px;
|
| 107 |
+
padding: 20px;
|
|
|
|
|
|
|
|
|
|
| 108 |
}
|
| 109 |
|
| 110 |
+
/* Result Cards */
|
| 111 |
.result-card {
|
| 112 |
background: #0f172a;
|
| 113 |
border-radius: 12px;
|
|
|
|
| 226 |
</div>
|
| 227 |
""", unsafe_allow_html=True)
|
| 228 |
|
| 229 |
+
# PURE MARKDOWN FOOTER (No HTML Tags)
|
| 230 |
st.markdown("<br><br><br><br>", unsafe_allow_html=True)
|
| 231 |
+
st.divider()
|
| 232 |
+
st.caption("Disclaimer: Medivio is an AI-powered analysis tool. It does not provide medical diagnosis. Always consult a qualified healthcare professional for medical advice.")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
|
| 234 |
+
# --- ABOUT (FIXED: PURE STREAMLIT) ---
|
| 235 |
def show_about():
|
| 236 |
if st.button("← Back"): go_to('landing')
|
| 237 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 238 |
|
| 239 |
+
# Title
|
| 240 |
+
st.title("About Medivio")
|
| 241 |
|
| 242 |
+
# Intro Text (Native Markdown)
|
| 243 |
st.markdown("""
|
| 244 |
+
Medivio is an intelligent interface for your health. It acts as a secure bridge between
|
| 245 |
+
complex medical data—like X-Rays, MRI scans, and doctor's notes—and clear human understanding.
|
| 246 |
+
""")
|
| 247 |
+
|
| 248 |
+
st.divider()
|
| 249 |
+
|
| 250 |
+
# Mission Section (Native Streamlit Columns)
|
| 251 |
+
col_a, col_b = st.columns(2)
|
| 252 |
+
|
| 253 |
+
with col_a:
|
| 254 |
+
st.subheader("Our Mission")
|
| 255 |
+
st.markdown("""
|
| 256 |
+
To democratize access to medical information. We believe everyone should be able to
|
| 257 |
+
understand their own health records instantly, without waiting days for an appointment.
|
| 258 |
+
""")
|
| 259 |
|
| 260 |
+
with col_b:
|
| 261 |
+
st.subheader("Technology")
|
| 262 |
+
st.markdown("""
|
| 263 |
+
Medivio leverages **Google Gemini 1.5 Pro**, a multimodal AI model capable of processing
|
| 264 |
+
vision (images) and text simultaneously. This allows for context-aware analysis that
|
| 265 |
+
mimics the reasoning of a medical professional.
|
| 266 |
+
""")
|
| 267 |
+
|
| 268 |
+
st.divider()
|
| 269 |
+
|
| 270 |
+
# Features (Native Columns, No HTML Injection)
|
| 271 |
+
st.subheader("How It Works")
|
| 272 |
+
f1, f2, f3 = st.columns(3)
|
| 273 |
+
|
| 274 |
+
with f1:
|
| 275 |
+
st.info("**1. Upload**\n\nDrag & Drop medical scans or paste clinical notes securely.")
|
| 276 |
|
| 277 |
+
with f2:
|
| 278 |
+
st.info("**2. Analyze**\n\nOur AI engine detects patterns, risks, and anomalies instantly.")
|
|
|
|
|
|
|
|
|
|
| 279 |
|
| 280 |
+
with f3:
|
| 281 |
+
st.success("**3. Understand**\n\nReceive a clear, jargon-free explanation of your health data.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
|
| 283 |
# --- AUTH ---
|
| 284 |
def show_auth():
|
| 285 |
c1, c2, c3 = st.columns([1,1,1])
|
| 286 |
with c2:
|
| 287 |
st.markdown("<br><br>", unsafe_allow_html=True)
|
|
|
|
| 288 |
|
| 289 |
+
# Simple Container
|
| 290 |
+
with st.container():
|
| 291 |
+
if st.session_state.auth_mode == 'login':
|
| 292 |
+
st.markdown("<h2 style='text-align:center; margin-bottom:10px;'>Member Login</h2>", unsafe_allow_html=True)
|
| 293 |
+
email = st.text_input("Email", key="l_e")
|
| 294 |
+
pw = st.text_input("Password", type="password", key="l_p")
|
| 295 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 296 |
+
if st.button("Sign In"):
|
| 297 |
+
if login_user(email, pw):
|
| 298 |
+
st.session_state.logged_in=True
|
| 299 |
+
st.session_state.user_email=email
|
| 300 |
+
go_to('dashboard')
|
| 301 |
+
else: st.error("Invalid Credentials")
|
|
|
|
|
|
|
|
|
|
| 302 |
|
| 303 |
+
st.markdown("---")
|
| 304 |
+
if st.button("Create Account", type="secondary"):
|
| 305 |
+
st.session_state.auth_mode = 'register'
|
| 306 |
+
st.rerun()
|
| 307 |
+
|
| 308 |
+
else:
|
| 309 |
+
st.markdown("<h2 style='text-align:center; margin-bottom:10px;'>Create Account</h2>", unsafe_allow_html=True)
|
| 310 |
+
re = st.text_input("Email", key="r_e")
|
| 311 |
+
rp = st.text_input("Password", type="password", key="r_p")
|
| 312 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 313 |
+
if st.button("Sign Up"):
|
| 314 |
+
if register_user(re, rp):
|
| 315 |
+
st.success("Account created! Please sign in.")
|
| 316 |
+
time.sleep(1.5)
|
| 317 |
+
st.session_state.auth_mode = 'login'
|
| 318 |
+
st.rerun()
|
| 319 |
+
else: st.error("Email already used.")
|
| 320 |
+
|
| 321 |
+
st.markdown("---")
|
| 322 |
+
if st.button("Back to Login", type="secondary"):
|
| 323 |
st.session_state.auth_mode = 'login'
|
| 324 |
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
|
| 326 |
# --- DASHBOARD ---
|
| 327 |
def show_dashboard():
|
|
|
|
| 358 |
st.title("Diagnostic Interface")
|
| 359 |
|
| 360 |
if st.session_state.analysis_result is None:
|
| 361 |
+
# Native Container for Upload
|
| 362 |
+
with st.container():
|
| 363 |
+
st.write("Upload your medical data below.")
|
| 364 |
+
img_files = st.file_uploader("Upload Medical Scans (X-Ray, MRI, CT)", type=['png','jpg','jpeg'], accept_multiple_files=True)
|
| 365 |
+
c1, c2 = st.columns([2, 1])
|
| 366 |
+
with c1: txt_context = st.text_area("Patient Symptoms / Clinical Context", height=100, placeholder="E.g. Patient has chest pain for 3 weeks...")
|
| 367 |
+
with c2: mode = st.selectbox("Analysis Mode", ["Radiologist Expert", "Simple Explanation"])
|
| 368 |
+
|
| 369 |
+
st.markdown("<br>", unsafe_allow_html=True)
|
| 370 |
+
if st.button("Run Analysis"):
|
| 371 |
+
if not img_files and not txt_context: st.error("Please upload an image or provide text.")
|
| 372 |
+
else:
|
| 373 |
+
with st.spinner("Processing Multi-Modal Data..."):
|
| 374 |
+
pil_images = [Image.open(x) for x in img_files] if img_files else []
|
| 375 |
+
res = get_gemini_analysis(pil_images, txt_context, mode)
|
| 376 |
+
st.session_state.analysis_result = res
|
| 377 |
+
st.session_state.analysis_images = pil_images
|
| 378 |
+
|
| 379 |
+
parts = res.split("|||")
|
| 380 |
+
if len(parts) >= 5:
|
| 381 |
+
title = parts[0].strip().replace("Part 1:", "").replace("**", "").strip()
|
| 382 |
+
risk_lvl = parts[3].strip()
|
| 383 |
+
add_history(st.session_state.user_email, title, res, risk_lvl)
|
| 384 |
+
else:
|
| 385 |
+
add_history(st.session_state.user_email, "Analysis", res, "Unknown")
|
| 386 |
+
st.rerun()
|
| 387 |
|
| 388 |
else:
|
| 389 |
res = st.session_state.analysis_result
|
|
|
|
| 398 |
parts = res.split("|||")
|
| 399 |
if len(parts) >= 5:
|
| 400 |
title, obs, risks, severity, actions = parts[0], parts[1], parts[2], parts[3], parts[4]
|
| 401 |
+
|
| 402 |
sev_clean = severity.strip().lower()
|
| 403 |
bar_class = "risk-fill-low"
|
| 404 |
color = "#22c55e"
|
| 405 |
if "medium" in sev_clean: bar_class = "risk-fill-med"; color = "#eab308"
|
| 406 |
if "high" in sev_clean: bar_class = "risk-fill-high"; color = "#ef4444"
|
| 407 |
|
| 408 |
+
# Severity Bar (HTML is required here for custom CSS visuals, but kept simple)
|
| 409 |
st.markdown(f"""
|
| 410 |
+
<div style='background:rgba(255,255,255,0.05); padding:20px; border-radius:10px; border-left:5px solid {color};'>
|
| 411 |
+
<h4 style='margin:0; color:{color}'>SEVERITY: {severity.upper()}</h4>
|
|
|
|
| 412 |
</div>
|
| 413 |
""", unsafe_allow_html=True)
|
| 414 |
|