Update src/streamlit_app.py
Browse files- src/streamlit_app.py +59 -50
src/streamlit_app.py
CHANGED
|
@@ -13,8 +13,8 @@ from streamlit_lottie import st_lottie
|
|
| 13 |
# 0. CONFIGURATION
|
| 14 |
# -----------------------------------------------------------------------------
|
| 15 |
st.set_page_config(
|
| 16 |
-
page_title="
|
| 17 |
-
page_icon="
|
| 18 |
layout="wide",
|
| 19 |
initial_sidebar_state="expanded"
|
| 20 |
)
|
|
@@ -35,10 +35,10 @@ genai.configure(api_key=GOOGLE_API_KEY)
|
|
| 35 |
model = genai.GenerativeModel('gemini-2.5-pro')
|
| 36 |
|
| 37 |
# -----------------------------------------------------------------------------
|
| 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,23 +57,21 @@ 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 |
-
|
| 75 |
-
history = conn.execute("SELECT type, date FROM history WHERE email=? ORDER BY id DESC LIMIT 5", (email,)).fetchall()
|
| 76 |
-
# Get last login date (simplified logic: using last scan date or join date)
|
| 77 |
last_scan = history[0][1] if history else "New User"
|
| 78 |
conn.close()
|
| 79 |
return history, last_scan
|
|
@@ -84,7 +82,7 @@ init_db()
|
|
| 84 |
# 2. SESSION STATE & CSS
|
| 85 |
# -----------------------------------------------------------------------------
|
| 86 |
if 'page' not in st.session_state: st.session_state.page = 'landing'
|
| 87 |
-
if 'auth_mode' not in st.session_state: st.session_state.auth_mode = 'login'
|
| 88 |
if 'logged_in' not in st.session_state: st.session_state.logged_in = False
|
| 89 |
if 'user_email' not in st.session_state: st.session_state.user_email = ""
|
| 90 |
if 'analysis_result' not in st.session_state: st.session_state.analysis_result = None
|
|
@@ -131,10 +129,11 @@ st.markdown("""
|
|
| 131 |
|
| 132 |
.history-item {
|
| 133 |
font-size: 0.85rem;
|
| 134 |
-
padding:
|
| 135 |
border-bottom: 1px solid rgba(255,255,255,0.05);
|
| 136 |
display: flex;
|
| 137 |
justify-content: space-between;
|
|
|
|
| 138 |
}
|
| 139 |
.history-date { color: #64748b; font-size: 0.75rem; }
|
| 140 |
|
|
@@ -150,14 +149,16 @@ def get_gemini_analysis(images, text_context, mode):
|
|
| 150 |
if mode == "Radiologist Expert": role = "a senior radiologist. Use precise technical terminology."
|
| 151 |
elif mode == "Simple Explanation": role = "a compassionate doctor explaining to a patient. Use simple analogies."
|
| 152 |
|
|
|
|
| 153 |
prompt = [
|
| 154 |
-
f"You are
|
| 155 |
f"Analyze these medical images and context: '{text_context}'.",
|
| 156 |
-
"Strictly format your output into
|
| 157 |
-
"Part 1:
|
| 158 |
-
"Part 2:
|
| 159 |
-
"Part 3:
|
| 160 |
-
"Part 4:
|
|
|
|
| 161 |
"Do not use Markdown headers. Just raw text for each section."
|
| 162 |
]
|
| 163 |
content = prompt + images
|
|
@@ -165,15 +166,14 @@ def get_gemini_analysis(images, text_context, mode):
|
|
| 165 |
response = model.generate_content(content)
|
| 166 |
return response.text
|
| 167 |
except Exception as e:
|
| 168 |
-
return f"Error|||System Error|||Low|||{str(e)}"
|
| 169 |
|
| 170 |
def chat_with_scan(user_query):
|
| 171 |
-
# Context-Aware Prompt
|
| 172 |
prev_findings = st.session_state.analysis_result
|
| 173 |
images = st.session_state.analysis_images
|
| 174 |
|
| 175 |
context_prompt = [
|
| 176 |
-
"You are
|
| 177 |
f"Here is your previous analysis summary: {prev_findings}",
|
| 178 |
"The user is asking a follow-up question. Do NOT re-analyze the whole image from scratch unless asked.",
|
| 179 |
"Answer the specific question based on the findings above.",
|
|
@@ -201,16 +201,17 @@ def do_sign_out():
|
|
| 201 |
# 4. PAGES
|
| 202 |
# -----------------------------------------------------------------------------
|
| 203 |
|
| 204 |
-
# --- LANDING ---
|
| 205 |
def show_landing():
|
| 206 |
c1, c2 = st.columns([1, 6])
|
| 207 |
-
with c1: st.markdown("<h3 style='color:#3b82f6; margin:0;'>
|
| 208 |
st.markdown("<br><br>", unsafe_allow_html=True)
|
| 209 |
|
| 210 |
col1, col2 = st.columns([1.3, 1])
|
| 211 |
with col1:
|
| 212 |
-
|
| 213 |
-
st.markdown("<
|
|
|
|
| 214 |
|
| 215 |
b1, b2 = st.columns([1,1.5])
|
| 216 |
with b1:
|
|
@@ -229,22 +230,22 @@ def show_landing():
|
|
| 229 |
st.markdown("<br><br><br><br>", unsafe_allow_html=True)
|
| 230 |
st.markdown("""
|
| 231 |
<div style='text-align:center; font-size:0.8rem; color:#475569; border-top:1px solid #1e293b; padding-top:20px;'>
|
| 232 |
-
<b>Disclaimer:</b>
|
| 233 |
Always consult a qualified healthcare professional for medical advice.
|
| 234 |
</div>
|
| 235 |
""", unsafe_allow_html=True)
|
| 236 |
|
| 237 |
-
# --- ABOUT ---
|
| 238 |
def show_about():
|
| 239 |
if st.button("← Back"): go_to('landing')
|
| 240 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 241 |
|
| 242 |
st.markdown("""
|
| 243 |
<div style='max-width: 700px; margin: 0 auto; text-align: center;'>
|
| 244 |
-
<h1 style='color:#3b82f6;'>About
|
| 245 |
<p style='font-size:1.1rem; line-height:1.8; color:#cbd5e1; margin-bottom:30px;'>
|
| 246 |
-
|
| 247 |
-
It acts as a bridge between raw diagnostic information (like X-
|
| 248 |
and human understanding.
|
| 249 |
</p>
|
| 250 |
|
|
@@ -268,7 +269,7 @@ def show_about():
|
|
| 268 |
</div>
|
| 269 |
""", unsafe_allow_html=True)
|
| 270 |
|
| 271 |
-
# --- AUTH
|
| 272 |
def show_auth():
|
| 273 |
c1, c2, c3 = st.columns([1,1,1])
|
| 274 |
with c2:
|
|
@@ -299,9 +300,9 @@ def show_auth():
|
|
| 299 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 300 |
if st.button("Sign Up"):
|
| 301 |
if register_user(re, rp):
|
| 302 |
-
st.success("Account created!")
|
| 303 |
-
time.sleep(1)
|
| 304 |
-
st.session_state.auth_mode = 'login'
|
| 305 |
st.rerun()
|
| 306 |
else: st.error("Email already used.")
|
| 307 |
|
|
@@ -327,17 +328,15 @@ def show_dashboard():
|
|
| 327 |
st.rerun()
|
| 328 |
|
| 329 |
st.markdown("---")
|
| 330 |
-
# FIX: Small "Last active" text on top of history
|
| 331 |
st.markdown(f"<div style='font-size:0.75rem; color:#64748b; margin-bottom:10px;'>Last active: {last_active}</div>", unsafe_allow_html=True)
|
| 332 |
-
st.markdown("#####
|
| 333 |
|
| 334 |
if history_data:
|
| 335 |
-
for
|
| 336 |
-
# FIX: Clean list - Title and Date only
|
| 337 |
st.markdown(f"""
|
| 338 |
<div class='history-item'>
|
| 339 |
-
<span>{
|
| 340 |
-
<span class='history-date'>{h_date
|
| 341 |
</div>
|
| 342 |
""", unsafe_allow_html=True)
|
| 343 |
else:
|
|
@@ -348,7 +347,6 @@ def show_dashboard():
|
|
| 348 |
|
| 349 |
st.title("Diagnostic Interface")
|
| 350 |
|
| 351 |
-
# INPUT SECTION
|
| 352 |
if st.session_state.analysis_result is None:
|
| 353 |
st.markdown("<div class='feature-card' style='text-align:left;'>", unsafe_allow_html=True)
|
| 354 |
img_files = st.file_uploader("Upload Medical Scans (X-Ray, MRI, CT)", type=['png','jpg','jpeg'], accept_multiple_files=True)
|
|
@@ -357,7 +355,7 @@ def show_dashboard():
|
|
| 357 |
with c2: mode = st.selectbox("Analysis Mode", ["Radiologist Expert", "Simple Explanation"])
|
| 358 |
|
| 359 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 360 |
-
if st.button("Run
|
| 361 |
if not img_files and not txt_context: st.error("Please upload an image or provide text.")
|
| 362 |
else:
|
| 363 |
with st.spinner("Processing Multi-Modal Data..."):
|
|
@@ -365,13 +363,20 @@ def show_dashboard():
|
|
| 365 |
res = get_gemini_analysis(pil_images, txt_context, mode)
|
| 366 |
st.session_state.analysis_result = res
|
| 367 |
st.session_state.analysis_images = pil_images
|
|
|
|
|
|
|
| 368 |
parts = res.split("|||")
|
| 369 |
-
|
| 370 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
st.rerun()
|
| 372 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 373 |
|
| 374 |
-
# RESULT SECTION
|
| 375 |
else:
|
| 376 |
res = st.session_state.analysis_result
|
| 377 |
images = st.session_state.analysis_images
|
|
@@ -381,9 +386,11 @@ def show_dashboard():
|
|
| 381 |
if st.button("💬 Chat with Scan"): go_to('chat')
|
| 382 |
|
| 383 |
st.markdown("---")
|
|
|
|
| 384 |
parts = res.split("|||")
|
| 385 |
-
if len(parts) >=
|
| 386 |
-
obs, risks, severity, actions = parts[0], parts[1], parts[2], parts[3]
|
|
|
|
| 387 |
sev_clean = severity.strip().lower()
|
| 388 |
bar_class = "risk-fill-low"
|
| 389 |
color = "#22c55e"
|
|
@@ -397,6 +404,8 @@ def show_dashboard():
|
|
| 397 |
</div>
|
| 398 |
""", unsafe_allow_html=True)
|
| 399 |
|
|
|
|
|
|
|
| 400 |
c1, c2 = st.columns(2)
|
| 401 |
with c1:
|
| 402 |
st.markdown(f"<div class='result-card' style='border-color:#3b82f6'><h4 style='color:#3b82f6'>🔍 Findings</h4>{obs}</div>", unsafe_allow_html=True)
|
|
@@ -433,7 +442,7 @@ def show_chat():
|
|
| 433 |
# 5. ROUTING
|
| 434 |
# -----------------------------------------------------------------------------
|
| 435 |
if st.session_state.page == 'landing': show_landing()
|
| 436 |
-
elif st.session_state.page == 'login': show_auth()
|
| 437 |
elif st.session_state.page == 'dashboard':
|
| 438 |
if st.session_state.logged_in: show_dashboard()
|
| 439 |
else: go_to('login')
|
|
|
|
| 13 |
# 0. CONFIGURATION
|
| 14 |
# -----------------------------------------------------------------------------
|
| 15 |
st.set_page_config(
|
| 16 |
+
page_title="Medivio | Medical Clarity",
|
| 17 |
+
page_icon="⚕️",
|
| 18 |
layout="wide",
|
| 19 |
initial_sidebar_state="expanded"
|
| 20 |
)
|
|
|
|
| 35 |
model = genai.GenerativeModel('gemini-2.5-pro')
|
| 36 |
|
| 37 |
# -----------------------------------------------------------------------------
|
| 38 |
+
# 1. DATABASE SYSTEM (Renamed to Medivio)
|
| 39 |
# -----------------------------------------------------------------------------
|
| 40 |
def init_db():
|
| 41 |
+
conn = sqlite3.connect('medivio.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.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.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.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.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()
|
| 77 |
return history, last_scan
|
|
|
|
| 82 |
# 2. SESSION STATE & CSS
|
| 83 |
# -----------------------------------------------------------------------------
|
| 84 |
if 'page' not in st.session_state: st.session_state.page = 'landing'
|
| 85 |
+
if 'auth_mode' not in st.session_state: st.session_state.auth_mode = 'login'
|
| 86 |
if 'logged_in' not in st.session_state: st.session_state.logged_in = False
|
| 87 |
if 'user_email' not in st.session_state: st.session_state.user_email = ""
|
| 88 |
if 'analysis_result' not in st.session_state: st.session_state.analysis_result = None
|
|
|
|
| 129 |
|
| 130 |
.history-item {
|
| 131 |
font-size: 0.85rem;
|
| 132 |
+
padding: 10px;
|
| 133 |
border-bottom: 1px solid rgba(255,255,255,0.05);
|
| 134 |
display: flex;
|
| 135 |
justify-content: space-between;
|
| 136 |
+
color: #cbd5e1;
|
| 137 |
}
|
| 138 |
.history-date { color: #64748b; font-size: 0.75rem; }
|
| 139 |
|
|
|
|
| 149 |
if mode == "Radiologist Expert": role = "a senior radiologist. Use precise technical terminology."
|
| 150 |
elif mode == "Simple Explanation": role = "a compassionate doctor explaining to a patient. Use simple analogies."
|
| 151 |
|
| 152 |
+
# Prompt Updated for Medivio
|
| 153 |
prompt = [
|
| 154 |
+
f"You are Medivio, {role}.",
|
| 155 |
f"Analyze these medical images and context: '{text_context}'.",
|
| 156 |
+
"Strictly format your output into 5 parts separated by '|||'.",
|
| 157 |
+
"Part 1: A Short, Descriptive Title (Max 4 words, e.g. 'Chest X-Ray Normal', 'Skin Rash Check')",
|
| 158 |
+
"Part 2: Clinical Findings (What is seen?)",
|
| 159 |
+
"Part 3: Risk Assessment (Low/Medium/High)",
|
| 160 |
+
"Part 4: Severity Score (Just the word: Low, Medium, or High)",
|
| 161 |
+
"Part 5: Recommended Actions",
|
| 162 |
"Do not use Markdown headers. Just raw text for each section."
|
| 163 |
]
|
| 164 |
content = prompt + images
|
|
|
|
| 166 |
response = model.generate_content(content)
|
| 167 |
return response.text
|
| 168 |
except Exception as e:
|
| 169 |
+
return f"Error|||System Error|||Low|||Low|||{str(e)}"
|
| 170 |
|
| 171 |
def chat_with_scan(user_query):
|
|
|
|
| 172 |
prev_findings = st.session_state.analysis_result
|
| 173 |
images = st.session_state.analysis_images
|
| 174 |
|
| 175 |
context_prompt = [
|
| 176 |
+
"You are Medivio. You have ALREADY analyzed this patient's scan.",
|
| 177 |
f"Here is your previous analysis summary: {prev_findings}",
|
| 178 |
"The user is asking a follow-up question. Do NOT re-analyze the whole image from scratch unless asked.",
|
| 179 |
"Answer the specific question based on the findings above.",
|
|
|
|
| 201 |
# 4. PAGES
|
| 202 |
# -----------------------------------------------------------------------------
|
| 203 |
|
| 204 |
+
# --- LANDING (UPDATED TEXT) ---
|
| 205 |
def show_landing():
|
| 206 |
c1, c2 = st.columns([1, 6])
|
| 207 |
+
with c1: st.markdown("<h3 style='color:#3b82f6; margin:0;'>MEDIVIO</h3>", unsafe_allow_html=True)
|
| 208 |
st.markdown("<br><br>", unsafe_allow_html=True)
|
| 209 |
|
| 210 |
col1, col2 = st.columns([1.3, 1])
|
| 211 |
with col1:
|
| 212 |
+
# Option 4 Text Implementation
|
| 213 |
+
st.markdown("<h1 style='font-size: 4rem; line-height: 1.1; margin-bottom:20px;'>Upload your medical report.<br><span style='background: -webkit-linear-gradient(0deg, #3b82f6, #a855f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent;'>Get answers in seconds.</span></h1>", unsafe_allow_html=True)
|
| 214 |
+
st.markdown("<p style='color:#94a3b8; font-size:1.2rem; line-height:1.6;'>AI that translates scans, labs, and clinical notes into language you understand.</p>", unsafe_allow_html=True)
|
| 215 |
|
| 216 |
b1, b2 = st.columns([1,1.5])
|
| 217 |
with b1:
|
|
|
|
| 230 |
st.markdown("<br><br><br><br>", unsafe_allow_html=True)
|
| 231 |
st.markdown("""
|
| 232 |
<div style='text-align:center; font-size:0.8rem; color:#475569; border-top:1px solid #1e293b; padding-top:20px;'>
|
| 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 (REMOVED TECH JARGON) ---
|
| 239 |
def show_about():
|
| 240 |
if st.button("← Back"): go_to('landing')
|
| 241 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 242 |
|
| 243 |
st.markdown("""
|
| 244 |
<div style='max-width: 700px; margin: 0 auto; text-align: center;'>
|
| 245 |
+
<h1 style='color:#3b82f6;'>About Medivio</h1>
|
| 246 |
<p style='font-size:1.1rem; line-height:1.8; color:#cbd5e1; margin-bottom:30px;'>
|
| 247 |
+
Medivio is an AI interface designed to interpret medical data.
|
| 248 |
+
It acts as a bridge between raw diagnostic information (like X-Rays and MRI scans)
|
| 249 |
and human understanding.
|
| 250 |
</p>
|
| 251 |
|
|
|
|
| 269 |
</div>
|
| 270 |
""", unsafe_allow_html=True)
|
| 271 |
|
| 272 |
+
# --- AUTH ---
|
| 273 |
def show_auth():
|
| 274 |
c1, c2, c3 = st.columns([1,1,1])
|
| 275 |
with c2:
|
|
|
|
| 300 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 301 |
if st.button("Sign Up"):
|
| 302 |
if register_user(re, rp):
|
| 303 |
+
st.success("Account created! Please sign in.")
|
| 304 |
+
time.sleep(1.5)
|
| 305 |
+
st.session_state.auth_mode = 'login'
|
| 306 |
st.rerun()
|
| 307 |
else: st.error("Email already used.")
|
| 308 |
|
|
|
|
| 328 |
st.rerun()
|
| 329 |
|
| 330 |
st.markdown("---")
|
|
|
|
| 331 |
st.markdown(f"<div style='font-size:0.75rem; color:#64748b; margin-bottom:10px;'>Last active: {last_active}</div>", unsafe_allow_html=True)
|
| 332 |
+
st.markdown("##### History")
|
| 333 |
|
| 334 |
if history_data:
|
| 335 |
+
for h_title, h_date in history_data:
|
|
|
|
| 336 |
st.markdown(f"""
|
| 337 |
<div class='history-item'>
|
| 338 |
+
<span>{h_title}</span>
|
| 339 |
+
<span class='history-date'>{h_date}</span>
|
| 340 |
</div>
|
| 341 |
""", unsafe_allow_html=True)
|
| 342 |
else:
|
|
|
|
| 347 |
|
| 348 |
st.title("Diagnostic Interface")
|
| 349 |
|
|
|
|
| 350 |
if st.session_state.analysis_result is None:
|
| 351 |
st.markdown("<div class='feature-card' style='text-align:left;'>", unsafe_allow_html=True)
|
| 352 |
img_files = st.file_uploader("Upload Medical Scans (X-Ray, MRI, CT)", type=['png','jpg','jpeg'], accept_multiple_files=True)
|
|
|
|
| 355 |
with c2: mode = st.selectbox("Analysis Mode", ["Radiologist Expert", "Simple Explanation"])
|
| 356 |
|
| 357 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 358 |
+
if st.button("Run Analysis"):
|
| 359 |
if not img_files and not txt_context: st.error("Please upload an image or provide text.")
|
| 360 |
else:
|
| 361 |
with st.spinner("Processing Multi-Modal Data..."):
|
|
|
|
| 363 |
res = get_gemini_analysis(pil_images, txt_context, mode)
|
| 364 |
st.session_state.analysis_result = res
|
| 365 |
st.session_state.analysis_images = pil_images
|
| 366 |
+
|
| 367 |
+
# PARSE (Title is Part 1)
|
| 368 |
parts = res.split("|||")
|
| 369 |
+
|
| 370 |
+
if len(parts) >= 5:
|
| 371 |
+
title = parts[0].strip().replace("Part 1:", "").replace("**", "").strip()
|
| 372 |
+
risk_lvl = parts[3].strip()
|
| 373 |
+
add_history(st.session_state.user_email, title, res, risk_lvl)
|
| 374 |
+
else:
|
| 375 |
+
add_history(st.session_state.user_email, "Scan Analysis", res, "Unknown")
|
| 376 |
+
|
| 377 |
st.rerun()
|
| 378 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 379 |
|
|
|
|
| 380 |
else:
|
| 381 |
res = st.session_state.analysis_result
|
| 382 |
images = st.session_state.analysis_images
|
|
|
|
| 386 |
if st.button("💬 Chat with Scan"): go_to('chat')
|
| 387 |
|
| 388 |
st.markdown("---")
|
| 389 |
+
|
| 390 |
parts = res.split("|||")
|
| 391 |
+
if len(parts) >= 5:
|
| 392 |
+
title, obs, risks, severity, actions = parts[0], parts[1], parts[2], parts[3], parts[4]
|
| 393 |
+
|
| 394 |
sev_clean = severity.strip().lower()
|
| 395 |
bar_class = "risk-fill-low"
|
| 396 |
color = "#22c55e"
|
|
|
|
| 404 |
</div>
|
| 405 |
""", unsafe_allow_html=True)
|
| 406 |
|
| 407 |
+
st.markdown(f"### {title}")
|
| 408 |
+
|
| 409 |
c1, c2 = st.columns(2)
|
| 410 |
with c1:
|
| 411 |
st.markdown(f"<div class='result-card' style='border-color:#3b82f6'><h4 style='color:#3b82f6'>🔍 Findings</h4>{obs}</div>", unsafe_allow_html=True)
|
|
|
|
| 442 |
# 5. ROUTING
|
| 443 |
# -----------------------------------------------------------------------------
|
| 444 |
if st.session_state.page == 'landing': show_landing()
|
| 445 |
+
elif st.session_state.page == 'login': show_auth()
|
| 446 |
elif st.session_state.page == 'dashboard':
|
| 447 |
if st.session_state.logged_in: show_dashboard()
|
| 448 |
else: go_to('login')
|