Pontonkid commited on
Commit
6fdae13
Β·
verified Β·
1 Parent(s): a9f826f

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +35 -99
src/streamlit_app.py CHANGED
@@ -32,7 +32,7 @@ if not GOOGLE_API_KEY:
32
  st.stop()
33
 
34
  genai.configure(api_key=GOOGLE_API_KEY)
35
- model = genai.GenerativeModel('gemini-2.5-pro')
36
 
37
  # -----------------------------------------------------------------------------
38
  # 1. DATABASE SYSTEM
@@ -84,8 +84,8 @@ init_db()
84
  if 'page' not in st.session_state: st.session_state.page = 'landing'
85
  if 'logged_in' not in st.session_state: st.session_state.logged_in = False
86
  if 'user_email' not in st.session_state: st.session_state.user_email = ""
87
- if 'analysis_result' not in st.session_state: st.session_state.analysis_result = None # Stores the text result
88
- if 'analysis_images' not in st.session_state: st.session_state.analysis_images = [] # Stores images
89
  if 'chat_history' not in st.session_state: st.session_state.chat_history = []
90
 
91
  st.markdown("""
@@ -98,26 +98,21 @@ st.markdown("""
98
  .risk-fill-low { background: #22c55e; width: 33%; height: 100%; border-radius: 5px; }
99
  .risk-fill-med { background: #eab308; width: 66%; height: 100%; border-radius: 5px; }
100
  .risk-fill-high { background: #ef4444; width: 100%; height: 100%; border-radius: 5px; }
101
-
102
- /* Chat Bubbles */
103
  .chat-user { background: #27272a; padding: 10px; border-radius: 10px; margin-bottom: 5px; text-align: right; color: #e4e4e7; }
104
  .chat-ai { background: #1e3a8a; padding: 10px; border-radius: 10px; margin-bottom: 5px; text-align: left; color: white; }
105
-
106
  #MainMenu, footer, header {visibility: hidden;}
107
  </style>
108
  """, unsafe_allow_html=True)
109
 
110
  # -----------------------------------------------------------------------------
111
- # 3. CORE LOGIC
112
  # -----------------------------------------------------------------------------
113
  def get_gemini_analysis(images, text_context, mode):
114
- # 1. Mode Selection
115
  role = "a helpful medical assistant"
116
  if mode == "Radiologist Expert": role = "a senior radiologist. Use precise technical terminology."
117
  elif mode == "Simple Explanation": role = "a compassionate doctor explaining to a patient. Use simple analogies."
118
  elif mode == "Medical Student": role = "a medical professor teaching a student. Explain the 'why'."
119
 
120
- # 2. Prompt
121
  prompt = [
122
  f"You are SHINUI, {role}.",
123
  f"Analyze these medical images and this clinical context: '{text_context}'.",
@@ -128,10 +123,7 @@ def get_gemini_analysis(images, text_context, mode):
128
  "Part 4: Recommended Actions",
129
  "Do not use Markdown headers. Just raw text for each section."
130
  ]
131
-
132
- # 3. Build Content List (Text + Images)
133
  content = prompt + images
134
-
135
  try:
136
  response = model.generate_content(content)
137
  return response.text
@@ -139,35 +131,31 @@ def get_gemini_analysis(images, text_context, mode):
139
  return f"Error|||System Error|||Low|||{str(e)}"
140
 
141
  def chat_with_scan(user_query):
142
- # We provide the PREVIOUS ANALYSIS as context so it doesn't have to re-analyze everything from scratch
143
- # Ideally we pass images again if the API supports stateful chat, but for single-turn REST API:
144
- # We will pass the images + previous findings + user query.
145
-
146
  prev_findings = st.session_state.analysis_result
147
  images = st.session_state.analysis_images
148
-
149
  context_prompt = [
150
  "You are SHINUI. We have already analyzed the patient's scan. Here are the previous findings:",
151
  f"--- START FINDINGS ---\n{prev_findings}\n--- END FINDINGS ---",
152
  "The user has a follow-up question based on these findings. Answer directly and clearly.",
153
  f"User Question: {user_query}"
154
  ]
155
-
156
- # Combine images into the prompt again to ensure it can "look" at specific areas if asked
157
  full_content = context_prompt + images
158
-
159
  try:
160
  response = model.generate_content(full_content)
161
  return response.text
162
  except: return "I could not process that follow-up. Please try rephrasing."
163
 
164
- def set_page(p): st.session_state.page = p; st.rerun()
165
- def sign_out():
 
 
 
 
166
  st.session_state.logged_in=False
167
  st.session_state.analysis_result=None
168
  st.session_state.analysis_images=[]
169
  st.session_state.chat_history=[]
170
- set_page('landing')
171
 
172
  # -----------------------------------------------------------------------------
173
  # 4. PAGES
@@ -178,16 +166,18 @@ def show_landing():
178
  c1, c2 = st.columns([1, 6])
179
  with c1: st.markdown("<h3 style='color:#3b82f6'>SHINUI</h3>", unsafe_allow_html=True)
180
  st.markdown("<br><br>", unsafe_allow_html=True)
181
-
182
  col1, col2 = st.columns([1.2, 1])
183
  with col1:
184
  st.markdown("<h1 style='font-size: 3.5rem'>Medical Intelligence<br><span style='color:#3b82f6'>Redefined.</span></h1>", unsafe_allow_html=True)
185
  st.markdown("<p style='color:#a1a1aa; font-size:1.1rem'>Multi-image analysis, expert modes, and realtime AI chat.</p>", unsafe_allow_html=True)
186
  b1, b2 = st.columns([1,1])
 
 
187
  with b1:
188
- if st.button("Launch App"): set_page('login')
189
  with b2:
190
- if st.button("About"): set_page('about')
 
191
  with col2:
192
  st.markdown("<div class='meta-card' style='height:300px; display:flex; justify-content:center; align-items:center;'><h1>🧬</h1></div>", unsafe_allow_html=True)
193
 
@@ -204,7 +194,7 @@ def show_auth():
204
  if login_user(email, pw):
205
  st.session_state.logged_in=True
206
  st.session_state.user_email=email
207
- set_page('dashboard')
208
  else: st.error("Invalid")
209
  with tab2:
210
  re = st.text_input("Email", key="r_e")
@@ -216,148 +206,94 @@ def show_auth():
216
 
217
  # --- DASHBOARD ---
218
  def show_dashboard():
219
- # SIDEBAR
220
  total_scans, last_scan = get_user_stats(st.session_state.user_email)
221
-
222
  with st.sidebar:
223
  st.markdown(f"### πŸ‘€ {st.session_state.user_email}")
224
  st.markdown("<div style='color:#10b981; margin-bottom:10px'>● Online</div>", unsafe_allow_html=True)
225
-
226
- # Personal Dashboard Stats
227
  st.markdown("---")
228
  c1, c2 = st.columns(2)
229
  c1.metric("Total Scans", total_scans)
230
  c2.metric("Last Active", last_scan if last_scan != "N/A" else "New")
231
-
232
  st.markdown("---")
233
  if st.button("Start New Analysis"):
234
  st.session_state.analysis_result = None
235
  st.session_state.analysis_images = []
236
  st.session_state.chat_history = []
237
  st.rerun()
238
- if st.button("Sign Out"): sign_out()
239
 
240
- # MAIN UI
241
  st.title("Advanced Diagnostic Interface")
242
-
243
- # INPUT SECTION (If no result yet)
244
  if st.session_state.analysis_result is None:
245
  st.markdown("<div class='meta-card'>", unsafe_allow_html=True)
246
-
247
- # 1. Multi-Image
248
  img_files = st.file_uploader("Upload Medical Scans (X-Ray, MRI, CT)", type=['png','jpg','jpeg'], accept_multiple_files=True)
249
-
250
- # 2. Context & Mode
251
  c1, c2 = st.columns([2, 1])
252
- with c1:
253
- txt_context = st.text_area("Patient Symptoms / Clinical Context", height=100, placeholder="E.g. Patient has chest pain for 3 weeks...")
254
- with c2:
255
- mode = st.selectbox("Analysis Mode", ["Radiologist Expert", "Simple Explanation", "Medical Student", "Research Deep Dive"])
256
-
257
  if st.button("Run SHINUI Analysis"):
258
- if not img_files and not txt_context:
259
- st.error("Please upload an image or provide text.")
260
  else:
261
  with st.spinner("Processing Multi-Modal Data..."):
262
  pil_images = [Image.open(x) for x in img_files] if img_files else []
263
-
264
- # Call AI
265
  res = get_gemini_analysis(pil_images, txt_context, mode)
266
-
267
- # Store State
268
  st.session_state.analysis_result = res
269
  st.session_state.analysis_images = pil_images
270
-
271
- # Parse Risk for DB
272
  parts = res.split("|||")
273
  risk_lvl = parts[2].strip() if len(parts) > 2 else "Unknown"
274
  add_history(st.session_state.user_email, "Multi-Scan", res, risk_lvl)
275
  st.rerun()
276
  st.markdown("</div>", unsafe_allow_html=True)
277
-
278
- # RESULT SECTION (If result exists)
279
  else:
280
  res = st.session_state.analysis_result
281
  images = st.session_state.analysis_images
282
-
283
- # 1. Action Bar
284
  col_act1, col_act2 = st.columns([1, 4])
 
 
285
  with col_act1:
286
- if st.button("πŸ’¬ Chat with Scan"):
287
- set_page('chat')
288
 
289
  st.markdown("---")
290
-
291
- # 2. Parse Output
292
  parts = res.split("|||")
293
  if len(parts) >= 4:
294
  obs, risks, severity, actions = parts[0], parts[1], parts[2], parts[3]
295
-
296
- # SEVERITY BAR
297
  sev_clean = severity.strip().lower()
298
  bar_class = "risk-fill-low"
299
  color = "#22c55e"
300
  if "medium" in sev_clean: bar_class = "risk-fill-med"; color = "#eab308"
301
  if "high" in sev_clean: bar_class = "risk-fill-high"; color = "#ef4444"
302
-
303
- st.markdown(f"""
304
- <div class='meta-card'>
305
- <h4 style='margin:0; color:{color}'>SEVERITY SCORE: {severity.upper()}</h4>
306
- <div class='risk-bar'><div class='{bar_class}'></div></div>
307
- </div>
308
- """, unsafe_allow_html=True)
309
-
310
  c1, c2 = st.columns(2)
311
  with c1:
312
  st.markdown(f"<div class='meta-card'><h4 style='color:#3b82f6'>πŸ” Findings</h4>{obs}</div>", unsafe_allow_html=True)
313
  st.markdown(f"<div class='meta-card'><h4 style='color:#f59e0b'>⚠️ Risks</h4>{risks}</div>", unsafe_allow_html=True)
314
  with c2:
315
  st.markdown(f"<div class='meta-card'><h4 style='color:#10b981'>βœ… Protocol</h4>{actions}</div>", unsafe_allow_html=True)
316
- if images:
317
- # Use use_container_width instead of use_column_width to fix warning
318
- st.image(images[0], caption="Primary Scan Reference", use_container_width=True)
319
 
320
- # --- CHAT PAGE (Separate Page) ---
321
  def show_chat():
322
- st.button("← Back to Results", on_click=lambda: set_page('dashboard'))
323
-
324
  st.title("πŸ’¬ Chat with Scan")
325
-
326
- # Display Images Context (Small)
327
  if st.session_state.analysis_images:
328
  with st.expander("View Scans Reference"):
329
- # Show images in a row
330
  cols = st.columns(len(st.session_state.analysis_images))
331
  for idx, img in enumerate(st.session_state.analysis_images):
332
- with cols[idx]:
333
- st.image(img, width=150)
334
-
335
- # Chat Container
336
  chat_container = st.container()
337
-
338
- # Input at bottom
339
  user_input = st.chat_input("Ask a follow-up question about the findings...")
340
-
341
  if user_input:
342
- # 1. Add User Message
343
  st.session_state.chat_history.append({"role": "user", "content": user_input})
344
-
345
- # 2. Generate Response (Context Aware)
346
  with st.spinner("Consulting Analysis..."):
347
  ai_reply = chat_with_scan(user_input)
348
  st.session_state.chat_history.append({"role": "ai", "content": ai_reply})
349
-
350
- # Render Chat History
351
  with chat_container:
352
  for msg in st.session_state.chat_history:
353
- if msg['role'] == 'user':
354
- st.markdown(f"<div class='chat-user'>{msg['content']}</div>", unsafe_allow_html=True)
355
- else:
356
- st.markdown(f"<div class='chat-ai'>{msg['content']}</div>", unsafe_allow_html=True)
357
 
358
  # --- ABOUT ---
359
  def show_about():
360
- if st.button("← Back"): set_page('landing')
361
  st.markdown("<br><div class='meta-card'><h2>About SHINUI</h2><p>Advanced Medical AI.</p></div>", unsafe_allow_html=True)
362
 
363
  # -----------------------------------------------------------------------------
@@ -367,8 +303,8 @@ if st.session_state.page == 'landing': show_landing()
367
  elif st.session_state.page == 'login': show_auth()
368
  elif st.session_state.page == 'dashboard':
369
  if st.session_state.logged_in: show_dashboard()
370
- else: set_page('login')
371
  elif st.session_state.page == 'chat':
372
  if st.session_state.logged_in: show_chat()
373
- else: set_page('login')
374
  elif st.session_state.page == 'about': show_about()
 
32
  st.stop()
33
 
34
  genai.configure(api_key=GOOGLE_API_KEY)
35
+ model = genai.GenerativeModel('gemini-1.5-pro')
36
 
37
  # -----------------------------------------------------------------------------
38
  # 1. DATABASE SYSTEM
 
84
  if 'page' not in st.session_state: st.session_state.page = 'landing'
85
  if 'logged_in' not in st.session_state: st.session_state.logged_in = False
86
  if 'user_email' not in st.session_state: st.session_state.user_email = ""
87
+ if 'analysis_result' not in st.session_state: st.session_state.analysis_result = None
88
+ if 'analysis_images' not in st.session_state: st.session_state.analysis_images = []
89
  if 'chat_history' not in st.session_state: st.session_state.chat_history = []
90
 
91
  st.markdown("""
 
98
  .risk-fill-low { background: #22c55e; width: 33%; height: 100%; border-radius: 5px; }
99
  .risk-fill-med { background: #eab308; width: 66%; height: 100%; border-radius: 5px; }
100
  .risk-fill-high { background: #ef4444; width: 100%; height: 100%; border-radius: 5px; }
 
 
101
  .chat-user { background: #27272a; padding: 10px; border-radius: 10px; margin-bottom: 5px; text-align: right; color: #e4e4e7; }
102
  .chat-ai { background: #1e3a8a; padding: 10px; border-radius: 10px; margin-bottom: 5px; text-align: left; color: white; }
 
103
  #MainMenu, footer, header {visibility: hidden;}
104
  </style>
105
  """, unsafe_allow_html=True)
106
 
107
  # -----------------------------------------------------------------------------
108
+ # 3. LOGIC
109
  # -----------------------------------------------------------------------------
110
  def get_gemini_analysis(images, text_context, mode):
 
111
  role = "a helpful medical assistant"
112
  if mode == "Radiologist Expert": role = "a senior radiologist. Use precise technical terminology."
113
  elif mode == "Simple Explanation": role = "a compassionate doctor explaining to a patient. Use simple analogies."
114
  elif mode == "Medical Student": role = "a medical professor teaching a student. Explain the 'why'."
115
 
 
116
  prompt = [
117
  f"You are SHINUI, {role}.",
118
  f"Analyze these medical images and this clinical context: '{text_context}'.",
 
123
  "Part 4: Recommended Actions",
124
  "Do not use Markdown headers. Just raw text for each section."
125
  ]
 
 
126
  content = prompt + images
 
127
  try:
128
  response = model.generate_content(content)
129
  return response.text
 
131
  return f"Error|||System Error|||Low|||{str(e)}"
132
 
133
  def chat_with_scan(user_query):
 
 
 
 
134
  prev_findings = st.session_state.analysis_result
135
  images = st.session_state.analysis_images
 
136
  context_prompt = [
137
  "You are SHINUI. We have already analyzed the patient's scan. Here are the previous findings:",
138
  f"--- START FINDINGS ---\n{prev_findings}\n--- END FINDINGS ---",
139
  "The user has a follow-up question based on these findings. Answer directly and clearly.",
140
  f"User Question: {user_query}"
141
  ]
 
 
142
  full_content = context_prompt + images
 
143
  try:
144
  response = model.generate_content(full_content)
145
  return response.text
146
  except: return "I could not process that follow-up. Please try rephrasing."
147
 
148
+ # Navigation Helper (NO CALLBACKS)
149
+ def go_to(page):
150
+ st.session_state.page = page
151
+ st.rerun()
152
+
153
+ def do_sign_out():
154
  st.session_state.logged_in=False
155
  st.session_state.analysis_result=None
156
  st.session_state.analysis_images=[]
157
  st.session_state.chat_history=[]
158
+ go_to('landing')
159
 
160
  # -----------------------------------------------------------------------------
161
  # 4. PAGES
 
166
  c1, c2 = st.columns([1, 6])
167
  with c1: st.markdown("<h3 style='color:#3b82f6'>SHINUI</h3>", unsafe_allow_html=True)
168
  st.markdown("<br><br>", unsafe_allow_html=True)
 
169
  col1, col2 = st.columns([1.2, 1])
170
  with col1:
171
  st.markdown("<h1 style='font-size: 3.5rem'>Medical Intelligence<br><span style='color:#3b82f6'>Redefined.</span></h1>", unsafe_allow_html=True)
172
  st.markdown("<p style='color:#a1a1aa; font-size:1.1rem'>Multi-image analysis, expert modes, and realtime AI chat.</p>", unsafe_allow_html=True)
173
  b1, b2 = st.columns([1,1])
174
+
175
+ # FIXED: Normal buttons, no on_click callback
176
  with b1:
177
+ if st.button("Launch App"): go_to('login')
178
  with b2:
179
+ if st.button("About"): go_to('about')
180
+
181
  with col2:
182
  st.markdown("<div class='meta-card' style='height:300px; display:flex; justify-content:center; align-items:center;'><h1>🧬</h1></div>", unsafe_allow_html=True)
183
 
 
194
  if login_user(email, pw):
195
  st.session_state.logged_in=True
196
  st.session_state.user_email=email
197
+ go_to('dashboard')
198
  else: st.error("Invalid")
199
  with tab2:
200
  re = st.text_input("Email", key="r_e")
 
206
 
207
  # --- DASHBOARD ---
208
  def show_dashboard():
 
209
  total_scans, last_scan = get_user_stats(st.session_state.user_email)
 
210
  with st.sidebar:
211
  st.markdown(f"### πŸ‘€ {st.session_state.user_email}")
212
  st.markdown("<div style='color:#10b981; margin-bottom:10px'>● Online</div>", unsafe_allow_html=True)
 
 
213
  st.markdown("---")
214
  c1, c2 = st.columns(2)
215
  c1.metric("Total Scans", total_scans)
216
  c2.metric("Last Active", last_scan if last_scan != "N/A" else "New")
 
217
  st.markdown("---")
218
  if st.button("Start New Analysis"):
219
  st.session_state.analysis_result = None
220
  st.session_state.analysis_images = []
221
  st.session_state.chat_history = []
222
  st.rerun()
223
+ if st.button("Sign Out"): do_sign_out()
224
 
 
225
  st.title("Advanced Diagnostic Interface")
 
 
226
  if st.session_state.analysis_result is None:
227
  st.markdown("<div class='meta-card'>", unsafe_allow_html=True)
 
 
228
  img_files = st.file_uploader("Upload Medical Scans (X-Ray, MRI, CT)", type=['png','jpg','jpeg'], accept_multiple_files=True)
 
 
229
  c1, c2 = st.columns([2, 1])
230
+ with c1: txt_context = st.text_area("Patient Symptoms / Clinical Context", height=100, placeholder="E.g. Patient has chest pain for 3 weeks...")
231
+ with c2: mode = st.selectbox("Analysis Mode", ["Radiologist Expert", "Simple Explanation", "Medical Student", "Research Deep Dive"])
 
 
 
232
  if st.button("Run SHINUI Analysis"):
233
+ if not img_files and not txt_context: st.error("Please upload an image or provide text.")
 
234
  else:
235
  with st.spinner("Processing Multi-Modal Data..."):
236
  pil_images = [Image.open(x) for x in img_files] if img_files else []
 
 
237
  res = get_gemini_analysis(pil_images, txt_context, mode)
 
 
238
  st.session_state.analysis_result = res
239
  st.session_state.analysis_images = pil_images
 
 
240
  parts = res.split("|||")
241
  risk_lvl = parts[2].strip() if len(parts) > 2 else "Unknown"
242
  add_history(st.session_state.user_email, "Multi-Scan", res, risk_lvl)
243
  st.rerun()
244
  st.markdown("</div>", unsafe_allow_html=True)
 
 
245
  else:
246
  res = st.session_state.analysis_result
247
  images = st.session_state.analysis_images
 
 
248
  col_act1, col_act2 = st.columns([1, 4])
249
+
250
+ # FIXED: Normal button logic
251
  with col_act1:
252
+ if st.button("πŸ’¬ Chat with Scan"): go_to('chat')
 
253
 
254
  st.markdown("---")
 
 
255
  parts = res.split("|||")
256
  if len(parts) >= 4:
257
  obs, risks, severity, actions = parts[0], parts[1], parts[2], parts[3]
 
 
258
  sev_clean = severity.strip().lower()
259
  bar_class = "risk-fill-low"
260
  color = "#22c55e"
261
  if "medium" in sev_clean: bar_class = "risk-fill-med"; color = "#eab308"
262
  if "high" in sev_clean: bar_class = "risk-fill-high"; color = "#ef4444"
263
+ st.markdown(f"<div class='meta-card'><h4 style='margin:0; color:{color}'>SEVERITY SCORE: {severity.upper()}</h4><div class='risk-bar'><div class='{bar_class}'></div></div></div>", unsafe_allow_html=True)
 
 
 
 
 
 
 
264
  c1, c2 = st.columns(2)
265
  with c1:
266
  st.markdown(f"<div class='meta-card'><h4 style='color:#3b82f6'>πŸ” Findings</h4>{obs}</div>", unsafe_allow_html=True)
267
  st.markdown(f"<div class='meta-card'><h4 style='color:#f59e0b'>⚠️ Risks</h4>{risks}</div>", unsafe_allow_html=True)
268
  with c2:
269
  st.markdown(f"<div class='meta-card'><h4 style='color:#10b981'>βœ… Protocol</h4>{actions}</div>", unsafe_allow_html=True)
270
+ if images: st.image(images[0], caption="Primary Scan Reference", use_container_width=True)
 
 
271
 
272
+ # --- CHAT PAGE ---
273
  def show_chat():
274
+ # FIXED: Normal button logic
275
+ if st.button("← Back to Results"): go_to('dashboard')
276
  st.title("πŸ’¬ Chat with Scan")
 
 
277
  if st.session_state.analysis_images:
278
  with st.expander("View Scans Reference"):
 
279
  cols = st.columns(len(st.session_state.analysis_images))
280
  for idx, img in enumerate(st.session_state.analysis_images):
281
+ with cols[idx]: st.image(img, width=150)
 
 
 
282
  chat_container = st.container()
 
 
283
  user_input = st.chat_input("Ask a follow-up question about the findings...")
 
284
  if user_input:
 
285
  st.session_state.chat_history.append({"role": "user", "content": user_input})
 
 
286
  with st.spinner("Consulting Analysis..."):
287
  ai_reply = chat_with_scan(user_input)
288
  st.session_state.chat_history.append({"role": "ai", "content": ai_reply})
 
 
289
  with chat_container:
290
  for msg in st.session_state.chat_history:
291
+ if msg['role'] == 'user': st.markdown(f"<div class='chat-user'>{msg['content']}</div>", unsafe_allow_html=True)
292
+ else: st.markdown(f"<div class='chat-ai'>{msg['content']}</div>", unsafe_allow_html=True)
 
 
293
 
294
  # --- ABOUT ---
295
  def show_about():
296
+ if st.button("← Back"): go_to('landing')
297
  st.markdown("<br><div class='meta-card'><h2>About SHINUI</h2><p>Advanced Medical AI.</p></div>", unsafe_allow_html=True)
298
 
299
  # -----------------------------------------------------------------------------
 
303
  elif st.session_state.page == 'login': show_auth()
304
  elif st.session_state.page == 'dashboard':
305
  if st.session_state.logged_in: show_dashboard()
306
+ else: go_to('login')
307
  elif st.session_state.page == 'chat':
308
  if st.session_state.logged_in: show_chat()
309
+ else: go_to('login')
310
  elif st.session_state.page == 'about': show_about()