Pontonkid commited on
Commit
16bb456
·
verified ·
1 Parent(s): cbd8e16

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +336 -368
src/streamlit_app.py CHANGED
@@ -1,18 +1,4 @@
1
 
2
- """
3
- Shinui Streamlit App - Futuristic Dark Theme (Option B)
4
- - Gemeni model set to gemini-2.5-pro
5
- - Landing page, About (pre-auth), Auth, Dashboard
6
- - Neon/dark UI + Lottie animations
7
- - Compatible with Hugging Face Spaces (Streamlit)
8
-
9
- Files included in this single code file:
10
- - The main Streamlit app code below
11
- - requirements.txt contents appended at bottom (commented)
12
-
13
- Save this as `shinui_app.py` when deploying to Spaces.
14
- """
15
-
16
  import streamlit as st
17
  import os
18
  import sqlite3
@@ -25,19 +11,36 @@ import requests
25
  from streamlit_lottie import st_lottie
26
 
27
  # -----------------------------------------------------------------------------
28
- # AUTO-CONFIG (Hugging Face / Spaces friendly)
29
  # -----------------------------------------------------------------------------
 
 
 
 
 
 
 
 
30
  config_dir = ".streamlit"
31
  if not os.path.exists(config_dir):
32
  os.makedirs(config_dir)
33
  with open(os.path.join(config_dir, "config.toml"), "w") as f:
34
  f.write("[server]\nenableXsrfProtection=false\nenableCORS=false\nmaxUploadSize=200\n")
35
 
 
 
 
 
 
 
 
 
 
36
  # -----------------------------------------------------------------------------
37
- # DATABASE SETUP
38
  # -----------------------------------------------------------------------------
39
  def init_db():
40
- conn = sqlite3.connect('users.db')
41
  c = conn.cursor()
42
  c.execute('''CREATE TABLE IF NOT EXISTS users
43
  (email TEXT PRIMARY KEY, password TEXT, joined_date TEXT)''')
@@ -46,13 +49,11 @@ def init_db():
46
  conn.commit()
47
  conn.close()
48
 
49
-
50
  def hash_pass(password):
51
  return hashlib.sha256(str.encode(password)).hexdigest()
52
 
53
-
54
  def register_user(email, password):
55
- conn = sqlite3.connect('users.db')
56
  c = conn.cursor()
57
  try:
58
  c.execute("INSERT INTO users VALUES (?, ?, ?)", (email, hash_pass(password), datetime.now().strftime("%Y-%m-%d")))
@@ -63,437 +64,404 @@ def register_user(email, password):
63
  finally:
64
  conn.close()
65
 
66
-
67
  def login_user(email, password):
68
- conn = sqlite3.connect('users.db')
69
  c = conn.cursor()
70
  c.execute("SELECT * FROM users WHERE email=? AND password=?", (email, hash_pass(password)))
71
  data = c.fetchall()
72
  conn.close()
73
  return data
74
 
75
-
76
  def add_history(email, type, summary):
77
- conn = sqlite3.connect('users.db')
78
  c = conn.cursor()
 
 
79
  c.execute("INSERT INTO history (email, type, summary, date) VALUES (?, ?, ?, ?)",
80
- (email, type, summary, datetime.now().strftime("%Y-%m-%d %H:%M")))
81
  conn.commit()
82
  conn.close()
83
 
84
-
85
  def get_history(email):
86
- conn = sqlite3.connect('users.db')
87
  c = conn.cursor()
88
  c.execute("SELECT type, summary, date FROM history WHERE email=? ORDER BY id DESC LIMIT 10", (email,))
89
  data = c.fetchall()
90
  conn.close()
91
  return data
92
 
93
- # Init DB
94
  init_db()
95
 
96
  # -----------------------------------------------------------------------------
97
- # AI CONFIGURATION
98
  # -----------------------------------------------------------------------------
99
- st.set_page_config(page_title="Shinui | Medical MVP", page_icon="✨", layout="wide")
100
-
101
- GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
102
- if not GOOGLE_API_KEY:
103
- st.error("⚠️ System Error: Google API Key Missing. Set the environment variable GOOGLE_API_KEY in Spaces.")
104
- st.stop()
105
-
106
- # Configure genai
107
- genai.configure(api_key=GOOGLE_API_KEY)
108
- # Use the model the user asked for
109
- model = genai.GenerativeModel('gemini-2.5-pro')
110
-
111
- # -----------------------------------------------------------------------------
112
- # STATE MANAGEMENT
113
- # -----------------------------------------------------------------------------
114
- if 'page' not in st.session_state: st.session_state.page = 'landing'
115
- if 'logged_in' not in st.session_state: st.session_state.logged_in = False
116
- if 'user_email' not in st.session_state: st.session_state.user_email = ""
117
- if 'current_result' not in st.session_state: st.session_state.current_result = None
118
-
119
- # -----------------------------------------------------------------------------
120
- # ASSET HELPERS (Lottie)
121
- # -----------------------------------------------------------------------------
122
- def load_lottie_url(url: str):
123
- try:
124
- r = requests.get(url)
125
- if r.status_code == 200:
126
- return r.json()
127
- return None
128
- except Exception:
129
  return None
 
130
 
131
- # Example Lottie URLs (replace if you have better assets)
132
- LOTTIE_HERO = "https://assets2.lottiefiles.com/packages/lf20_jtbfg2nb.json"
133
- LOTTIE_MEDICAL = "https://assets4.lottiefiles.com/packages/lf20_x62chJ.json"
134
- LOTTIE_LOADING = "https://assets6.lottiefiles.com/packages/lf20_usmfx6bp.json"
135
 
136
- lottie_hero = load_lottie_url(LOTTIE_HERO)
137
- lottie_med = load_lottie_url(LOTTIE_MEDICAL)
138
- lottie_load = load_lottie_url(LOTTIE_LOADING)
 
 
139
 
140
  # -----------------------------------------------------------------------------
141
- # UI STYLING (Futuristic Dark + Neon)
142
  # -----------------------------------------------------------------------------
143
  st.markdown("""
144
  <style>
145
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap');
146
- :root{
147
- --bg: #0b1220;
148
- --card: #0f1724;
149
- --muted: #94a3b8;
150
- --accent: #00f5ff;
151
- --accent-2: #7c3aed;
152
- --glass: rgba(255,255,255,0.03);
153
- }
154
  .stApp {
155
- background: linear-gradient(180deg, var(--bg) 0%, #071024 100%);
156
- color: #e6eef8;
157
  font-family: 'Inter', sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  }
159
- /* Cards */
160
- .shinui-card{ background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01)); border:1px solid rgba(255,255,255,0.04); border-radius:12px; padding:18px; }
161
- .hero-cta{ background: linear-gradient(90deg, var(--accent), var(--accent-2)); padding:12px 22px; border-radius:10px; color:#001219; font-weight:700; }
162
- /* Buttons */
163
- div.stButton > button{ background:transparent; border:1px solid rgba(255,255,255,0.06); padding:10px 16px; border-radius:10px; color:var(--accent); font-weight:700; }
164
- div.stButton > button:hover{ transform:translateY(-2px); box-shadow:0 10px 30px rgba(124,58,237,0.12); }
165
- /* Inputs */
166
- .stTextInput input, .stTextArea textarea{ background: rgba(255,255,255,0.02) !important; color: #e6eef8 !important; border:1px solid rgba(255,255,255,0.04) !important; }
167
- /* Sidebar */
168
- [data-testid="stSidebar"]{ background: linear-gradient(180deg, rgba(124,58,237,0.06), rgba(0,245,255,0.02)); border-right:1px solid rgba(255,255,255,0.02); }
169
- /* Result badges */
170
- .risk-badge{ background: linear-gradient(90deg, rgba(255,69,96,0.12), rgba(255,69,96,0.06)); padding:8px 12px; border-radius:8px; display:inline-block; }
171
- .safe-badge{ background: linear-gradient(90deg, rgba(34,197,94,0.12), rgba(34,197,94,0.06)); padding:8px 12px; border-radius:8px; display:inline-block; }
172
- /* Hide Streamlit header */
173
- #MainMenu, footer, header {visibility: hidden;}
174
  </style>
175
  """, unsafe_allow_html=True)
176
 
177
  # -----------------------------------------------------------------------------
178
- # LOGIC: Gemini call wrapper
179
  # -----------------------------------------------------------------------------
 
 
 
 
180
 
181
- def get_gemini_insight(input_type, content):
 
 
 
 
182
  prompt = """
183
- You are Shinui, a professional medical AI assistant.
184
- Analyze the input carefully and format your response using these EXACT headers:
185
-
186
- ### 1. Clinical Observation
187
- (Describe what you see or read clearly)
188
-
189
- ### 2. Potential Risks
190
- (List potential conditions or risks associated)
191
-
192
- ### 3. Recommended Actions
193
- (Suggest next steps, e.g., see a specialist, apply ice, etc.)
194
- """
 
195
  try:
196
  if input_type == "Image":
197
- # If content is a PIL Image, convert to bytes for upload if needed by API
198
- response = model.generate_content([prompt, "Image data: (embedded)"])
199
- return response.text
200
  else:
201
  response = model.generate_content(f"{prompt}\n\nInput Data: {content}")
202
- return response.text
203
  except Exception as e:
204
- return f"Error: {str(e)}"
205
 
206
  # -----------------------------------------------------------------------------
207
- # UI PAGES
208
  # -----------------------------------------------------------------------------
209
 
210
- def nav_to(page):
211
- st.session_state.page = page
212
- st.experimental_rerun()
213
-
214
-
215
- def sign_out():
216
- st.session_state.logged_in = False
217
- st.session_state.user_email = ""
218
- st.session_state.current_result = None
219
- nav_to('landing')
220
-
221
- # --- PUBLIC LANDING PAGE ---
222
-
223
  def show_landing():
224
- st.markdown("""
225
- <div style='display:flex; align-items:center; gap:34px;'>
226
- <div style='flex:1'>
227
- <h1 style='font-size:42px; margin:0; color: white'>Shinui — Medical AI Assistant</h1>
228
- <p style='color: #9fb3d8; font-size:18px; margin-top:8px;'>Fast, explainable medical analysis for images and notes — MVP-ready and hackathon polished.</p>
229
- <div style='margin-top:18px; display:flex; gap:12px;'>
230
- <button class='hero-cta' onclick="window.parent.postMessage({type: 'nav', page: 'about'}, '*')">Learn More</button>
231
- </div>
232
- </div>
233
- <div style='width:420px;'>
234
- """, unsafe_allow_html=True)
235
- if lottie_hero:
236
- st_lottie(lottie_hero, height=300)
237
- else:
238
- st.image(Image.new('RGB', (420,300), color=(10,20,40)))
239
-
240
- st.markdown("""
241
- </div>
242
- </div>
243
- <hr style='border-color: rgba(255,255,255,0.03); margin-top:18px;'/>
244
- """, unsafe_allow_html=True)
245
-
246
- # Capabilities
247
- c1, c2, c3 = st.columns(3)
248
  with c1:
 
 
 
 
 
 
 
249
  st.markdown("""
250
- <div class='shinui-card'>
251
- <h3>Visual Analysis</h3>
252
- <p class='muted'>Upload scans or photos; get structured observations.</p>
253
- </div>
254
- """, unsafe_allow_html=True)
255
- with c2:
256
- st.markdown("""
257
- <div class='shinui-card'>
258
- <h3>Symptoms & Notes</h3>
259
- <p class='muted'>Paste clinical notes or symptoms for instant triage.</p>
260
- </div>
261
  """, unsafe_allow_html=True)
262
- with c3:
 
 
 
 
 
 
 
 
 
 
263
  st.markdown("""
264
- <div class='shinui-card'>
265
- <h3>Explainability</h3>
266
- <p class='muted'>Clear steps and risk categories with action items.</p>
 
267
  </div>
268
  """, unsafe_allow_html=True)
269
 
270
- st.markdown("""
271
- <div style='margin-top:18px; display:flex; gap:10px;'>
272
- <div style='width:160px;'>
273
- <button onclick="window.parent.postMessage({type: 'nav', page: 'auth'}, '*')">Get Started</button>
274
- </div>
275
- <div style='width:180px;'>
276
- <button onclick="window.parent.postMessage({type: 'nav', page: 'about'}, '*')">About Shinui</button>
277
- </div>
278
- </div>
279
- """, unsafe_allow_html=True)
280
-
281
- # --- ABOUT (PUBLIC) ---
282
-
283
- def show_public_about():
284
- st.markdown("""
285
- <div class='shinui-card'>
286
- <h2 style='color:#00f5ff'>About Shinui</h2>
287
- <p style='color:#b9d6ff'>Shinui is an MVP medical assistant that transforms images and notes into easy-to-action medical observations.</p>
288
- <hr style='border-color: rgba(255,255,255,0.03)'>
289
- <h3>How it works</h3>
290
- <ol>
291
- <li>Upload an image or paste notes.</li>
292
- <li>Shinui analyzes using a Gemini model and returns structured output.</li>
293
- <li>Results include observations, potential risks, and recommended actions.</li>
294
- </ol>
295
- <h3>Safety</h3>
296
- <p>Shinui is an assistant — not a diagnosis tool. Always consult a licensed professional for critical decisions.</p>
297
- <div style='margin-top:12px;'>
298
- <button onclick="window.parent.postMessage({type: 'nav', page: 'auth'}, '*')">Proceed to Sign In</button>
299
- </div>
300
- </div>
301
- """, unsafe_allow_html=True)
302
-
303
- # --- AUTH PAGE ---
304
-
305
- def show_auth():
306
- st.markdown("<br>", unsafe_allow_html=True)
307
- c1, c2, c3 = st.columns([1, 2, 1])
308
- with c2:
309
  st.markdown("""
310
- <div class='shinui-card' style='text-align:center;'>
311
- <h2 style='margin-bottom:4px;'>Welcome to Shinui</h2>
312
- <p style='color:var(--muted); margin-top:0;'>Sign in or create an account to start your analysis.</p>
 
 
 
313
  </div>
314
  """, unsafe_allow_html=True)
315
 
316
- tab1, tab2 = st.tabs(["Sign In", "Create Account"])
317
- with tab1:
318
- email = st.text_input("Email Address", key="l_email")
319
- password = st.text_input("Password", type="password", key="l_pass")
320
- if st.button("Login"):
321
- if login_user(email, password):
322
- st.session_state.logged_in = True
323
- st.session_state.user_email = email
324
- st.success("Login Successful")
325
- time.sleep(0.4)
326
- nav_to('dashboard')
327
- else:
328
- st.error("Invalid email or password")
329
- with tab2:
330
- new_email = st.text_input("Email Address", key="r_email")
331
- new_pass = st.text_input("Password", type="password", key="r_pass")
332
- confirm_pass = st.text_input("Confirm Password", type="password", key="r_pass2")
333
- if st.button("Register"):
334
- if new_pass != confirm_pass:
335
- st.error("Passwords do not match")
336
- elif register_user(new_email, new_pass):
337
- st.success("Account created! Please log in.")
338
- else:
339
- st.error("Email already exists")
340
-
341
- # --- DASHBOARD (AUTHENTICATED) ---
342
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  def show_dashboard():
 
344
  with st.sidebar:
345
  st.markdown(f"### 👤 {st.session_state.user_email}")
346
- st.markdown("<span style='color:#22c55e; font-size:0.8rem;'>● Online</span>", unsafe_allow_html=True)
 
 
 
 
 
347
  st.markdown("---")
348
- if st.button("📊 Dashboard"): nav_to('dashboard')
349
- if st.button("ℹ️ About Shinui"): nav_to('about')
350
- st.markdown("### Recent History")
351
  history = get_history(st.session_state.user_email)
352
  if history:
353
  for h_type, h_sum, h_date in history:
354
- st.markdown(f"<div style='font-size:0.8rem; padding:8px; background:rgba(255,255,255,0.02); border-radius:6px; margin-bottom:6px;'>\n<b>{h_type}</b><br>{h_date}<br><span style='color:#94a3b8'>{h_sum[:60]}...</span>\n</div>", unsafe_allow_html=True)
 
 
 
 
 
355
  else:
356
- st.caption("No activity found.")
 
357
  st.markdown("---")
358
  if st.button("Sign Out"): sign_out()
359
 
360
- st.title("Medical Analysis Dashboard")
361
- col1, col2 = st.columns([2, 1])
362
- with col1:
363
- st.markdown("### New Scan")
364
- t1, t2 = st.tabs(["📷 Visual Analysis", "📝 Symptom Check"])
365
- with t1:
366
- st.markdown("<div class='shinui-card'>", unsafe_allow_html=True)
367
- img = st.file_uploader("Upload Medical Image", type=['png','jpg','jpeg'])
368
- if img and st.button("Analyze Image"):
369
- image = Image.open(img)
370
- with st.spinner("Shinui AI Analyzing..."):
371
- # Show lottie while processing
372
- if lottie_load:
373
- st_lottie(lottie_load, height=80)
374
- res = get_gemini_insight("Image", image)
375
- st.session_state.current_result = res
376
- add_history(st.session_state.user_email, "Visual Scan", res[:140].replace('\n', ' '))
377
- st.markdown("</div>", unsafe_allow_html=True)
378
- with t2:
379
- st.markdown("<div class='shinui-card'>", unsafe_allow_html=True)
380
- txt = st.text_area("Describe Symptoms")
381
- if txt and st.button("Analyze Text"):
382
- with st.spinner("Shinui AI Analyzing..."):
383
- if lottie_load:
384
- st_lottie(lottie_load, height=80)
385
- res = get_gemini_insight("Text", txt)
386
- st.session_state.current_result = res
387
- add_history(st.session_state.user_email, "Text Analysis", res[:140].replace('\n', ' '))
388
- st.markdown("</div>", unsafe_allow_html=True)
389
-
390
- with col2:
391
- st.markdown("### Results")
 
 
 
 
 
392
  if st.session_state.current_result:
393
- content = st.session_state.current_result
394
- # Try to split into the three headers for nicer display
395
- obs = ''
396
- risks = ''
397
- actions = ''
398
- try:
399
- parts = content.split('###')
400
- for p in parts:
401
- if 'Clinical Observation' in p:
402
- obs = p.replace('1. Clinical Observation','').replace('Clinical Observation','').strip()
403
- if 'Potential Risks' in p:
404
- risks = p.replace('Potential Risks','').strip()
405
- if 'Recommended Actions' in p:
406
- actions = p.replace('Recommended Actions','').strip()
407
- except Exception:
408
- obs = content
409
- # Display neatly
410
- st.markdown("<div class='shinui-card' style='border-top:4px solid #7c3aed;'>", unsafe_allow_html=True)
411
- if obs:
412
- st.markdown(f"<h4>Clinical Observation</h4><div style='padding:8px; background:rgba(255,255,255,0.01); border-radius:8px'>{obs}</div>", unsafe_allow_html=True)
413
- if risks:
414
- st.markdown(f"<h4>Potential Risks</h4><div style='padding:8px'>{risks}</div>", unsafe_allow_html=True)
415
- if actions:
416
- st.markdown(f"<h4>Recommended Actions</h4><div style='padding:8px'>{actions}</div>", unsafe_allow_html=True)
417
- st.markdown("</div>", unsafe_allow_html=True)
 
418
  else:
419
- st.info("Upload an image or enter text to see analysis results here.")
420
-
421
- # --- ABOUT (AUTHENTICATED) ---
 
 
 
 
422
 
 
423
  def show_about():
424
- with st.sidebar:
425
- if st.button("← Back to Dashboard"): nav_to('dashboard')
426
  st.markdown("""
427
- <div class='shinui-card'>
428
- <h2 style='color:#00f5ff'>About Shinui</h2>
429
- <p style='font-size:1.05rem; line-height:1.6'>
430
- Shinui is an MVP-stage medical intelligence platform designed to democratize access to health data analysis.
 
431
  </p>
432
- <hr style='border-color:#112233'>
433
- <h3>The Problem</h3>
434
- <p>Medical data is complex, jargon-heavy, and hard for patients to interpret.</p>
435
- <h3>The Solution</h3>
436
- <p>Shinui uses Gemini 2.5 to act as a translator, turning images and clinical notes into actionable, understandable insights.</p>
437
- <h3>Version Info</h3>
438
- <p><b>Build:</b> v1.0 MVP</p>
439
- <p><b>Engine:</b> Gemini 2.5 Pro</p>
440
  </div>
441
  """, unsafe_allow_html=True)
442
 
443
  # -----------------------------------------------------------------------------
444
- # ROUTER: listens to postMessage nav events for buttons in raw HTML
445
  # -----------------------------------------------------------------------------
446
-
447
- def handle_post_messages():
448
- # Streamlit doesn't give a simple postMessage listener; we can use a workaround
449
- # Buttons in HTML above call window.parent.postMessage({type:'nav',page:'...'})
450
- # We'll inject JS to listen and set location hash as a signal
451
- st.markdown("""
452
- <script>
453
- window.addEventListener('message', (e) => {
454
- try{
455
- if(e.data && e.data.type === 'nav'){
456
- const page = e.data.page;
457
- window.location.hash = page;
458
- }
459
- }catch(err){}
460
- });
461
- // On hash change, reload the page so Streamlit picks up
462
- window.addEventListener('hashchange', function(){ window.location.reload(); });
463
- </script>
464
- """, unsafe_allow_html=True)
465
-
466
- # Read location hash and map to streamlit pages
467
- hash = st.experimental_get_query_params().get('_', [''])[0]
468
- # Also check window.location.hash via JS by reading document.location.hash into query param isn't straightforward
469
- # We'll instead read the hash from window.location via a short JS that sets a query param — keep it simple here
470
-
471
- # -----------------------------------------------------------------------------
472
- # MAIN
473
- # -----------------------------------------------------------------------------
474
-
475
- def main():
476
- # Map basic routing
477
- # Show landing or auth pages when not logged in
478
- if st.session_state.page == 'landing':
479
- show_landing()
480
- show_public_about()
481
- elif st.session_state.page == 'auth':
482
- show_auth()
483
- elif st.session_state.page == 'dashboard':
484
- if st.session_state.logged_in:
485
- show_dashboard()
486
- else:
487
- nav_to('auth')
488
- elif st.session_state.page == 'about':
489
- # If logged in, show authenticated about; else show public about
490
- if st.session_state.logged_in:
491
- show_about()
492
- else:
493
- show_public_about()
494
-
495
- # Basic post message handler
496
- handle_post_messages()
497
-
498
- if __name__ == '__main__':
499
- main()
 
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import streamlit as st
3
  import os
4
  import sqlite3
 
11
  from streamlit_lottie import st_lottie
12
 
13
  # -----------------------------------------------------------------------------
14
+ # 0. CONFIGURATION & SETUP
15
  # -----------------------------------------------------------------------------
16
+ st.set_page_config(
17
+ page_title="SHINUI | Medical Intelligence",
18
+ page_icon="🧬",
19
+ layout="wide",
20
+ initial_sidebar_state="expanded"
21
+ )
22
+
23
+ # Fix Upload Error (Auto-Config)
24
  config_dir = ".streamlit"
25
  if not os.path.exists(config_dir):
26
  os.makedirs(config_dir)
27
  with open(os.path.join(config_dir, "config.toml"), "w") as f:
28
  f.write("[server]\nenableXsrfProtection=false\nenableCORS=false\nmaxUploadSize=200\n")
29
 
30
+ # API Setup
31
+ GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY")
32
+ if not GOOGLE_API_KEY:
33
+ st.error("⚠️ System Error: Google API Key is missing in Secrets.")
34
+ st.stop()
35
+
36
+ genai.configure(api_key=GOOGLE_API_KEY)
37
+ model = genai.GenerativeModel('gemini-1.5-pro')
38
+
39
  # -----------------------------------------------------------------------------
40
+ # 1. DATABASE & AUTH SYSTEM
41
  # -----------------------------------------------------------------------------
42
  def init_db():
43
+ conn = sqlite3.connect('shinui_users.db')
44
  c = conn.cursor()
45
  c.execute('''CREATE TABLE IF NOT EXISTS users
46
  (email TEXT PRIMARY KEY, password TEXT, joined_date TEXT)''')
 
49
  conn.commit()
50
  conn.close()
51
 
 
52
  def hash_pass(password):
53
  return hashlib.sha256(str.encode(password)).hexdigest()
54
 
 
55
  def register_user(email, password):
56
+ conn = sqlite3.connect('shinui_users.db')
57
  c = conn.cursor()
58
  try:
59
  c.execute("INSERT INTO users VALUES (?, ?, ?)", (email, hash_pass(password), datetime.now().strftime("%Y-%m-%d")))
 
64
  finally:
65
  conn.close()
66
 
 
67
  def login_user(email, password):
68
+ conn = sqlite3.connect('shinui_users.db')
69
  c = conn.cursor()
70
  c.execute("SELECT * FROM users WHERE email=? AND password=?", (email, hash_pass(password)))
71
  data = c.fetchall()
72
  conn.close()
73
  return data
74
 
 
75
  def add_history(email, type, summary):
76
+ conn = sqlite3.connect('shinui_users.db')
77
  c = conn.cursor()
78
+ # Store only the first 80 chars of summary for the list
79
+ clean_sum = summary.replace("|||", " ").replace("*", "")[:80] + "..."
80
  c.execute("INSERT INTO history (email, type, summary, date) VALUES (?, ?, ?, ?)",
81
+ (email, type, clean_sum, datetime.now().strftime("%Y-%m-%d %H:%M")))
82
  conn.commit()
83
  conn.close()
84
 
 
85
  def get_history(email):
86
+ conn = sqlite3.connect('shinui_users.db')
87
  c = conn.cursor()
88
  c.execute("SELECT type, summary, date FROM history WHERE email=? ORDER BY id DESC LIMIT 10", (email,))
89
  data = c.fetchall()
90
  conn.close()
91
  return data
92
 
 
93
  init_db()
94
 
95
  # -----------------------------------------------------------------------------
96
+ # 2. UTILITIES (Lottie & Navigation)
97
  # -----------------------------------------------------------------------------
98
+ def load_lottieurl(url):
99
+ r = requests.get(url)
100
+ if r.status_code != 200:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  return None
102
+ return r.json()
103
 
104
+ def nav_to(page):
105
+ st.session_state.page = page
106
+ st.rerun()
 
107
 
108
+ def sign_out():
109
+ st.session_state.logged_in = False
110
+ st.session_state.user_email = ""
111
+ st.session_state.current_result = None
112
+ nav_to('landing')
113
 
114
  # -----------------------------------------------------------------------------
115
+ # 3. THE "META-STYLE" CSS
116
  # -----------------------------------------------------------------------------
117
  st.markdown("""
118
  <style>
119
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap');
120
+
121
+ /* GLOBAL THEME */
 
 
 
 
 
 
122
  .stApp {
123
+ background-color: #09090b; /* Deep Zinc Black */
 
124
  font-family: 'Inter', sans-serif;
125
+ color: #e4e4e7;
126
+ }
127
+
128
+ /* HEADERS */
129
+ h1, h2, h3 {
130
+ font-weight: 800;
131
+ letter-spacing: -0.5px;
132
+ color: #ffffff;
133
+ }
134
+
135
+ /* CARDS */
136
+ .meta-card {
137
+ background-color: #18181b;
138
+ border: 1px solid #27272a;
139
+ border-radius: 12px;
140
+ padding: 24px;
141
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
142
+ }
143
+ .meta-card:hover {
144
+ border-color: #3b82f6;
145
+ box-shadow: 0 4px 20px rgba(59, 130, 246, 0.1);
146
+ }
147
+
148
+ /* RESULT BOXES (The Clean Look) */
149
+ .res-header {
150
+ font-size: 0.85rem;
151
+ text-transform: uppercase;
152
+ letter-spacing: 1px;
153
+ font-weight: 600;
154
+ margin-bottom: 10px;
155
+ display: flex;
156
+ align-items: center;
157
+ gap: 8px;
158
+ }
159
+ .box-obs { border-left: 3px solid #3b82f6; background: #111113; padding: 20px; border-radius: 0 8px 8px 0; margin-bottom: 15px; }
160
+ .box-risk { border-left: 3px solid #f59e0b; background: #111113; padding: 20px; border-radius: 0 8px 8px 0; margin-bottom: 15px; }
161
+ .box-act { border-left: 3px solid #10b981; background: #111113; padding: 20px; border-radius: 0 8px 8px 0; margin-bottom: 15px; }
162
+
163
+ /* BUTTONS */
164
+ div.stButton > button {
165
+ background: linear-gradient(to right, #2563eb, #3b82f6);
166
+ color: white;
167
+ border: none;
168
+ border-radius: 8px;
169
+ padding: 10px 24px;
170
+ font-weight: 600;
171
+ width: 100%;
172
+ transition: all 0.3s;
173
+ }
174
+ div.stButton > button:hover {
175
+ opacity: 0.9;
176
+ transform: scale(1.01);
177
+ }
178
+
179
+ /* INPUTS */
180
+ .stTextInput input, .stTextArea textarea {
181
+ background-color: #18181b !important;
182
+ border: 1px solid #27272a !important;
183
+ color: white !important;
184
+ border-radius: 8px;
185
  }
186
+
187
+ /* NAVBAR REMOVAL */
188
+ #MainMenu {visibility: hidden;}
189
+ footer {visibility: hidden;}
190
+ header {visibility: hidden;}
 
 
 
 
 
 
 
 
 
 
191
  </style>
192
  """, unsafe_allow_html=True)
193
 
194
  # -----------------------------------------------------------------------------
195
+ # 4. SESSION STATE
196
  # -----------------------------------------------------------------------------
197
+ if 'page' not in st.session_state: st.session_state.page = 'landing'
198
+ if 'logged_in' not in st.session_state: st.session_state.logged_in = False
199
+ if 'user_email' not in st.session_state: st.session_state.user_email = ""
200
+ if 'current_result' not in st.session_state: st.session_state.current_result = None
201
 
202
+ # -----------------------------------------------------------------------------
203
+ # 5. AI LOGIC (STRICT FORMATTING)
204
+ # -----------------------------------------------------------------------------
205
+ def get_gemini_analysis(input_type, content):
206
+ # We force a specific separator "|||" to split the UI into 3 cards later
207
  prompt = """
208
+ You are SHINUI, a high-level medical AI. Analyze the input.
209
+
210
+ Strictly format your response into 3 parts separated by the string "|||".
211
+
212
+ Part 1: Clinical Observation (What is visually or textually present?)
213
+ Part 2: Potential Risks (Pathologies or warnings)
214
+ Part 3: Recommended Actions (Next steps for the patient)
215
+
216
+ Do NOT use Markdown headers (#). Just raw text for each section.
217
+ Example output:
218
+ Patient shows signs of dermatitis... ||| Risk of infection if untreated... ||| Clean area and consult dermatologist.
219
+ """
220
+
221
  try:
222
  if input_type == "Image":
223
+ response = model.generate_content([prompt, content])
 
 
224
  else:
225
  response = model.generate_content(f"{prompt}\n\nInput Data: {content}")
226
+ return response.text
227
  except Exception as e:
228
+ return f"Error Processing Request.|||System could not interpret input.|||Try again with clearer data. Error: {str(e)}"
229
 
230
  # -----------------------------------------------------------------------------
231
+ # 6. PAGES
232
  # -----------------------------------------------------------------------------
233
 
234
+ # --- LANDING PAGE ---
 
 
 
 
 
 
 
 
 
 
 
 
235
  def show_landing():
236
+ # Navbar
237
+ c1, c2 = st.columns([1, 6])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  with c1:
239
+ st.markdown("<h3 style='margin:0; color:#3b82f6;'>SHINUI</h3>", unsafe_allow_html=True)
240
+
241
+ st.markdown("<br><br>", unsafe_allow_html=True)
242
+
243
+ col1, col2 = st.columns([1.2, 1])
244
+
245
+ with col1:
246
  st.markdown("""
247
+ <h1 style='font-size: 3.5rem; line-height: 1.1; margin-bottom: 20px;'>
248
+ Medical AI,<br>
249
+ <span style='color:#3b82f6;'>Reimagined.</span>
250
+ </h1>
251
+ <p style='font-size: 1.1rem; color: #a1a1aa; margin-bottom: 40px; line-height: 1.6;'>
252
+ SHINUI uses state-of-the-art multimodal AI to translate complex medical data
253
+ into clear, actionable insights. From X-rays to clinical notes,
254
+ get answers in seconds.
255
+ </p>
 
 
256
  """, unsafe_allow_html=True)
257
+
258
+ b1, b2, b3 = st.columns([1, 1, 1])
259
+ with b1:
260
+ if st.button("Login"): nav_to('login')
261
+ with b2:
262
+ if st.button("Register"): nav_to('register')
263
+ with b3:
264
+ if st.button("About"): nav_to('about')
265
+
266
+ # Stats
267
+ st.markdown("<br><br>", unsafe_allow_html=True)
268
  st.markdown("""
269
+ <div style='display:flex; gap:30px;'>
270
+ <div><h3 style='margin:0;'>99%</h3><small style='color:#71717a'>Uptime</small></div>
271
+ <div><h3 style='margin:0;'>1.5 Flash</h3><small style='color:#71717a'>Model Engine</small></div>
272
+ <div><h3 style='margin:0;'>24/7</h3><small style='color:#71717a'>Availability</small></div>
273
  </div>
274
  """, unsafe_allow_html=True)
275
 
276
+ with col2:
277
+ # Medical Animation (DNA or Brain)
278
+ lottie_url = "https://lottie.host/9f6a8843-5582-417f-b273-0938323a7492/123456.json"
279
+ # Fallback graphic if lottie fails (or offline)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  st.markdown("""
281
+ <div class='meta-card' style='height: 300px; display:flex; align-items:center; justify-content:center; text-align:center;'>
282
+ <div>
283
+ <div style='font-size: 4rem;'>🧬</div>
284
+ <h3 style='margin-top:10px;'>Multimodal Engine</h3>
285
+ <p style='color:#52525b;'>Vision • Text • Reasoning</p>
286
+ </div>
287
  </div>
288
  """, unsafe_allow_html=True)
289
 
290
+ # --- AUTH PAGES ---
291
+ def show_auth_login():
292
+ c1, c2, c3 = st.columns([1, 1, 1])
293
+ with c2:
294
+ st.markdown("<br><br>", unsafe_allow_html=True)
295
+ st.markdown("<div class='meta-card'>", unsafe_allow_html=True)
296
+ st.markdown("<h2 style='text-align:center'>Welcome Back</h2>", unsafe_allow_html=True)
297
+ email = st.text_input("Email", key="l_email")
298
+ password = st.text_input("Password", type="password", key="l_pass")
299
+
300
+ if st.button("Sign In"):
301
+ if login_user(email, password):
302
+ st.session_state.logged_in = True
303
+ st.session_state.user_email = email
304
+ nav_to('dashboard')
305
+ else:
306
+ st.error("Invalid email or password.")
307
+
308
+ st.markdown("<hr style='border-color:#27272a'>", unsafe_allow_html=True)
309
+ if st.button("Create Account", type="secondary"): nav_to('register')
310
+ if st.button("← Home", type="secondary"): nav_to('landing')
311
+ st.markdown("</div>", unsafe_allow_html=True)
312
+
313
+ def show_auth_register():
314
+ c1, c2, c3 = st.columns([1, 1, 1])
315
+ with c2:
316
+ st.markdown("<br><br>", unsafe_allow_html=True)
317
+ st.markdown("<div class='meta-card'>", unsafe_allow_html=True)
318
+ st.markdown("<h2 style='text-align:center'>Create Account</h2>", unsafe_allow_html=True)
319
+ email = st.text_input("Email", key="r_email")
320
+ p1 = st.text_input("Password", type="password", key="r_p1")
321
+ p2 = st.text_input("Confirm Password", type="password", key="r_p2")
322
+
323
+ if st.button("Sign Up"):
324
+ if p1 != p2:
325
+ st.error("Passwords do not match")
326
+ elif register_user(email, p1):
327
+ st.success("Account created! Please Login.")
328
+ time.sleep(1)
329
+ nav_to('login')
330
+ else:
331
+ st.error("Email already exists.")
332
+
333
+ if st.button("← Back to Login", type="secondary"): nav_to('login')
334
+ st.markdown("</div>", unsafe_allow_html=True)
335
+
336
+ # --- DASHBOARD ---
337
  def show_dashboard():
338
+ # SIDEBAR
339
  with st.sidebar:
340
  st.markdown(f"### 👤 {st.session_state.user_email}")
341
+ st.markdown("<div style='font-size:0.8rem; color:#10b981; margin-bottom:20px;'>● Secure Connection</div>", unsafe_allow_html=True)
342
+
343
+ if st.button("New Scan"):
344
+ st.session_state.current_result = None
345
+ st.rerun()
346
+
347
  st.markdown("---")
348
+ st.markdown("### History")
 
 
349
  history = get_history(st.session_state.user_email)
350
  if history:
351
  for h_type, h_sum, h_date in history:
352
+ st.markdown(f"""
353
+ <div style='border-left:2px solid #333; padding-left:10px; margin-bottom:10px;'>
354
+ <div style='font-size:0.7rem; color:#71717a'>{h_date}</div>
355
+ <div style='font-size:0.85rem; color:#e4e4e7'>{h_type}</div>
356
+ </div>
357
+ """, unsafe_allow_html=True)
358
  else:
359
+ st.caption("No previous scans.")
360
+
361
  st.markdown("---")
362
  if st.button("Sign Out"): sign_out()
363
 
364
+ # MAIN AREA
365
+ st.markdown("## Diagnostic Interface")
366
+
367
+ # Layout: Left = Input, Right = Output (Desktop Style)
368
+ col_input, col_output = st.columns([1, 1.2])
369
+
370
+ with col_input:
371
+ st.markdown("<div class='meta-card'>", unsafe_allow_html=True)
372
+ tab_img, tab_txt = st.tabs(["📷 Visual", "📝 Clinical Notes"])
373
+
374
+ analyze_trigger = False
375
+ input_val = None
376
+ input_type_str = ""
377
+
378
+ with tab_img:
379
+ img_file = st.file_uploader("Upload Scan / Image", type=['png','jpg','jpeg'])
380
+ if img_file and st.button("Analyze Visual Data"):
381
+ input_val = Image.open(img_file)
382
+ input_type_str = "Image"
383
+ analyze_trigger = True
384
+
385
+ with tab_txt:
386
+ txt_input = st.text_area("Paste Symptoms or Notes", height=150)
387
+ if txt_input and st.button("Analyze Text Data"):
388
+ input_val = txt_input
389
+ input_type_str = "Text"
390
+ analyze_trigger = True
391
+ st.markdown("</div>", unsafe_allow_html=True)
392
+
393
+ if analyze_trigger:
394
+ with st.spinner("SHINUI Neural Engine Processing..."):
395
+ res = get_gemini_analysis(input_type_str, input_val)
396
+ st.session_state.current_result = res
397
+ add_history(st.session_state.user_email, f"{input_type_str} Scan", res)
398
+ st.rerun()
399
+
400
+ with col_output:
401
  if st.session_state.current_result:
402
+ # Parsing Logic
403
+ raw = st.session_state.current_result
404
+ parts = raw.split("|||")
405
+
406
+ if len(parts) >= 3:
407
+ obs = parts[0].strip()
408
+ risks = parts[1].strip()
409
+ acts = parts[2].strip()
410
+
411
+ st.markdown(f"""
412
+ <div class='box-obs'>
413
+ <div class='res-header' style='color:#3b82f6'>🔍 Clinical Observation</div>
414
+ {obs}
415
+ </div>
416
+ <div class='box-risk'>
417
+ <div class='res-header' style='color:#f59e0b'>⚠️ Risk Assessment</div>
418
+ {risks}
419
+ </div>
420
+ <div class='box-act'>
421
+ <div class='res-header' style='color:#10b981'>✅ Protocol / Action</div>
422
+ {acts}
423
+ </div>
424
+ """, unsafe_allow_html=True)
425
+ else:
426
+ # Fallback for unstructured response
427
+ st.markdown(f"<div class='meta-card'>{raw}</div>", unsafe_allow_html=True)
428
  else:
429
+ # Empty State
430
+ st.markdown("""
431
+ <div class='meta-card' style='text-align:center; padding:50px; color:#52525b;'>
432
+ <h4>Ready for Analysis</h4>
433
+ <p>Upload data on the left to generate insights.</p>
434
+ </div>
435
+ """, unsafe_allow_html=True)
436
 
437
+ # --- ABOUT PAGE ---
438
  def show_about():
439
+ st.button("← Back", on_click=lambda: nav_to('landing'))
440
+ st.markdown("<br>", unsafe_allow_html=True)
441
  st.markdown("""
442
+ <div class='meta-card' style='max-width:800px; margin:0 auto;'>
443
+ <h1 style='color:#3b82f6'>About SHINUI</h1>
444
+ <p style='font-size:1.2rem; line-height:1.6; color:#d4d4d8;'>
445
+ SHINUI is an advanced medical intelligence platform designed to bridge the gap
446
+ between raw medical data and actionable human understanding.
447
  </p>
448
+ <hr style='border-color:#27272a'>
449
+ <h3>Technology</h3>
450
+ <p>Powered by <b>Google Gemini 1.5 Pro</b>, SHINUI utilizes a massive multimodal context window
451
+ to understand images, handwriting, and clinical syntax with near-human accuracy.</p>
452
+
453
+ <h3>Mission</h3>
454
+ <p>To democratize access to high-quality medical data interpretation.</p>
 
455
  </div>
456
  """, unsafe_allow_html=True)
457
 
458
  # -----------------------------------------------------------------------------
459
+ # 7. ROUTER
460
  # -----------------------------------------------------------------------------
461
+ if st.session_state.page == 'landing': show_landing()
462
+ elif st.session_state.page == 'login': show_auth_login()
463
+ elif st.session_state.page == 'register': show_auth_register()
464
+ elif st.session_state.page == 'dashboard':
465
+ if st.session_state.logged_in: show_dashboard()
466
+ else: nav_to('login')
467
+ elif st.session_state.page == 'about': show_about()