Pontonkid commited on
Commit
e621b51
·
verified ·
1 Parent(s): 6ee6fcd

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +150 -295
src/streamlit_app.py CHANGED
@@ -1,345 +1,200 @@
1
  import streamlit as st
 
2
  import time
3
  from datetime import datetime
 
 
4
 
5
  # -----------------------------------------------------------------------------
6
- # 1. APP CONFIGURATION
7
  # -----------------------------------------------------------------------------
8
- st.set_page_config(
9
- page_title="SHINUI | Intelligent Health AI",
10
- page_icon="✨",
11
- layout="wide",
12
- initial_sidebar_state="expanded"
13
- )
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  # -----------------------------------------------------------------------------
16
- # 2. SESSION STATE INITIALIZATION
17
  # -----------------------------------------------------------------------------
18
- if 'page' not in st.session_state: st.session_state.page = 'landing'
19
- if 'logged_in' not in st.session_state: st.session_state.logged_in = False
20
- if 'user_email' not in st.session_state: st.session_state.user_email = ""
21
- if 'history' not in st.session_state: st.session_state.history = []
22
- if 'result' not in st.session_state: st.session_state.result = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
  # -----------------------------------------------------------------------------
25
- # 3. VISUAL STYLING (PREMIUM DARK MODE + ANIMATIONS)
26
  # -----------------------------------------------------------------------------
 
 
 
27
  st.markdown("""
28
  <style>
29
  @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;600;800&display=swap');
30
-
31
- /* GLOBAL THEME */
32
  .stApp {
33
  background-color: #020617;
34
  background-image: radial-gradient(circle at 50% 0%, #1e293b 0%, #020617 70%);
35
  font-family: 'Plus Jakarta Sans', sans-serif;
36
  color: #f8fafc;
37
  }
38
-
39
- /* ANIMATIONS */
40
- @keyframes slideUp {
41
- 0% { opacity: 0; transform: translateY(20px); }
42
- 100% { opacity: 1; transform: translateY(0); }
43
- }
44
- .animate-slide-up {
45
- animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
46
- }
47
-
48
- /* CARDS */
49
  .shinui-card {
50
  background: rgba(30, 41, 59, 0.4);
51
  border: 1px solid rgba(148, 163, 184, 0.1);
52
  border-radius: 16px;
53
  padding: 25px;
54
  backdrop-filter: blur(12px);
55
- transition: all 0.3s ease;
56
  margin-bottom: 20px;
57
  }
58
- .shinui-card:hover {
59
- transform: translateY(-3px);
60
- border-color: #38bdf8;
61
- background: rgba(30, 41, 59, 0.6);
62
- box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.5);
63
- }
64
-
65
- /* BUTTONS */
66
  div.stButton > button {
67
- background: linear-gradient(135deg, #38bdf8 0%, #0ea5e9 100%);
68
- color: white;
69
  border: none;
70
- font-weight: 600;
71
- padding: 12px 24px;
72
  border-radius: 8px;
73
- transition: all 0.2s;
74
  width: 100%;
 
75
  }
76
  div.stButton > button:hover {
77
- transform: scale(1.02);
78
- box-shadow: 0 0 15px rgba(56, 189, 248, 0.4);
79
  }
80
 
81
- /* TEXT GRADIENTS */
82
- .highlight {
83
- background: linear-gradient(90deg, #38bdf8, #a855f7);
84
- -webkit-background-clip: text;
85
- -webkit-text-fill-color: transparent;
86
- font-weight: 800;
87
- }
88
 
89
- /* HIDE DEFAULT UI */
90
  #MainMenu, footer, header {visibility: hidden;}
91
  </style>
92
  """, unsafe_allow_html=True)
93
 
94
  # -----------------------------------------------------------------------------
95
- # 4. LOGIC FUNCTIONS
96
  # -----------------------------------------------------------------------------
97
- def nav_to(page):
98
- st.session_state.page = page
99
- st.rerun()
100
-
101
- def sign_out():
102
- """Securely clears session and redirects."""
103
- st.session_state.logged_in = False
104
- st.session_state.history = []
105
- st.session_state.result = None
106
- st.session_state.user_email = ""
107
- nav_to('landing')
108
-
109
- def process_analysis(input_type, content_name):
110
- """
111
- Simulates the high-end analysis without crashing the server.
112
- It adapts the response based on the input type.
113
- """
114
- with st.spinner(f"SHINUI Neural Engine processing {input_type}..."):
115
- time.sleep(2) # Cinematic delay for UI feel
116
-
117
- # Intelligent Mock Responses
118
- if input_type == "Image":
119
- summary = "Visual Scan: Detected pill bottle label with clear text indicators."
120
- action = "Check dosage instructions."
121
- elif input_type == "Audio":
122
- summary = "Vocal Analysis: Voice biomarkers indicate mild respiratory stress."
123
- action = "Monitor breathing patterns."
124
- elif input_type == "Text":
125
- summary = "Symptom NLP: Description matches common seasonal allergic reaction."
126
- action = "Consult pharmacist for antihistamines."
127
- elif input_type == "Face Scan":
128
- summary = "Dermatological Scan: No critical irregularities detected on skin surface."
129
- action = "Routine hydration recommended."
130
- else:
131
- summary = "Analysis complete."
132
- action = "Review details."
133
-
134
- return {
135
- "summary": summary,
136
- "risk": "Low to Moderate",
137
- "details": "The input data has been processed against our medical database. No immediate emergency flags were triggered.",
138
- "action": action,
139
- "date": datetime.now().strftime("%Y-%m-%d %H:%M")
140
- }
141
 
142
  # -----------------------------------------------------------------------------
143
- # 5. PAGES
144
  # -----------------------------------------------------------------------------
145
-
146
- # --- LANDING PAGE ---
147
- def show_landing():
148
- c1, c2 = st.columns([1, 8])
149
- with c1:
150
- st.markdown("<h3 style='margin:0; color:#38bdf8;'>SHINUI</h3>", unsafe_allow_html=True)
151
-
152
- st.markdown("<br><br>", unsafe_allow_html=True)
153
-
154
- col1, col2 = st.columns([1.5, 1])
155
- with col1:
156
- st.markdown('<div class="animate-slide-up">', unsafe_allow_html=True)
157
- st.markdown(f"""
158
- <h1 style='font-size: 4.5rem; line-height: 1.1; margin-bottom: 20px;'>
159
- Medical Intelligence.<br>
160
- <span class='highlight'>Simplified.</span>
161
- </h1>
162
- <p style='font-size: 1.2rem; color: #94a3b8; margin-bottom: 40px; max-width:90%;'>
163
- SHINUI bridges the gap between complex medical data and human understanding.
164
- Upload images, speak your symptoms, or scan documents for instant clarity.
165
- </p>
166
- """, unsafe_allow_html=True)
167
-
168
- b1, b2 = st.columns([1, 2])
169
- with b1:
170
- if st.button("Start Analysis"):
171
- nav_to('login')
172
- with b2:
173
- if st.button("About Tool"):
174
- nav_to('about')
175
- st.markdown('</div>', unsafe_allow_html=True)
176
-
177
- with col2:
178
- st.markdown('<div class="animate-slide-up" style="animation-delay: 0.2s;">', unsafe_allow_html=True)
179
- st.markdown("""
180
- <div class="shinui-card">
181
- <h3>🧬 Multimodal Engine</h3>
182
- <p style="color:#94a3b8; font-size:0.9rem;">Processing Video, Audio, and Text simultaneously.</p>
183
- </div>
184
- <div class="shinui-card">
185
- <h3>🔒 Privacy First</h3>
186
- <p style="color:#94a3b8; font-size:0.9rem;">Session-based data handling. No permanent storage.</p>
187
- </div>
188
- """, unsafe_allow_html=True)
189
- st.markdown('</div>', unsafe_allow_html=True)
190
-
191
- # --- ABOUT PAGE ---
192
- def show_about():
193
- if st.button("← Back"): nav_to('landing')
194
- st.markdown("<br>", unsafe_allow_html=True)
195
- st.markdown("""
196
- <div class="shinui-card animate-slide-up">
197
- <h2 class="highlight">About SHINUI</h2>
198
- <p style="font-size: 1.1rem; line-height: 1.8; color: #cbd5e1;">
199
- SHINUI is a dedicated AI health assistant designed to clarify medical confusion.
200
- It is not a replacement for a doctor, but a tool to help you organize, understand, and
201
- prepare for professional medical consultations.
202
- </p>
203
- <h4 style="margin-top:20px; color:white;">Our Mission</h4>
204
- <p style="color: #94a3b8;">To empower patients with knowledge through accessible technology, ensuring that medical data—whether handwritten notes, complex scans, or confusing labels—becomes clear, actionable information.</p>
205
  </div>
206
  """, unsafe_allow_html=True)
207
 
208
- # --- LOGIN PAGE ---
209
- def show_login():
210
- c1, c2, c3 = st.columns([1,1,1])
211
- with c2:
212
- st.markdown("<br><br>", unsafe_allow_html=True)
213
- st.markdown("""
214
- <div class="shinui-card" style="text-align:center;">
215
- <h2>Secure Access</h2>
216
- <p style="color:#94a3b8;">Sign in to access the Neural Engine.</p>
217
- </div>
218
- """, unsafe_allow_html=True)
219
-
220
- email = st.text_input("Email Identity")
221
- password = st.text_input("Passkey", type="password")
222
-
223
- if st.button("Authenticate"):
224
- if email:
225
- st.session_state.logged_in = True
226
- st.session_state.user_email = email
227
- nav_to('dashboard')
228
-
229
- if st.button("Cancel"): nav_to('landing')
230
-
231
- # --- DASHBOARD ---
232
- def show_dashboard():
233
- # Sidebar Logic
234
- with st.sidebar:
235
- st.markdown(f"### 👤 {st.session_state.user_email}")
236
- st.markdown("---")
237
- st.markdown("#### Recent Scans")
238
- if st.session_state.history:
239
- for item in reversed(st.session_state.history):
240
- st.markdown(f"""
241
- <div style="font-size:0.8rem; padding:10px; border-left:2px solid #38bdf8; margin-bottom:5px; background:rgba(255,255,255,0.03);">
242
- <div style="color:white;">{item['summary']}</div>
243
- <div style="color:#64748b; font-size:0.7rem;">{item['date']}</div>
244
- </div>
245
- """, unsafe_allow_html=True)
246
- else:
247
- st.caption("No history found.")
248
-
249
- st.markdown("---")
250
- if st.button("Sign Out"): sign_out()
251
-
252
- # Main Content
253
- st.title("Neural Diagnostic Interface")
254
-
255
- # TABS
256
- t1, t2, t3, t4 = st.tabs(["📷 Image/Video", "🎙️ Audio/Speech", "📝 Text Input", "👤 Face Scan"])
257
-
258
- input_data = None
259
- input_type = None
260
-
261
- # Tab 1: Image
262
- with t1:
263
- st.markdown("<div class='shinui-card'>", unsafe_allow_html=True)
264
- st.write("Upload Medical Imaging or Video.")
265
- f = st.file_uploader("Select File", type=['png','jpg','jpeg', 'mp4'], label_visibility="collapsed", key="img_uploader")
266
- if f and st.button("Analyze Image"):
267
- input_data = f.name
268
- input_type = "Image"
269
- st.markdown("</div>", unsafe_allow_html=True)
270
-
271
- # Tab 2: Audio (Using File Uploader for stability, or native input if available)
272
- with t2:
273
- st.markdown("<div class='shinui-card'>", unsafe_allow_html=True)
274
- st.write("Record or Upload audio description.")
275
- # We use file uploader to be safe on older streamlit versions, works 100%
276
- audio_file = st.file_uploader("Upload Audio", type=['wav','mp3'], label_visibility="collapsed", key="audio_uploader")
277
- if audio_file and st.button("Process Audio"):
278
- input_data = "Audio File"
279
- input_type = "Audio"
280
- st.markdown("</div>", unsafe_allow_html=True)
281
-
282
- # Tab 3: Text
283
- with t3:
284
- st.markdown("<div class='shinui-card'>", unsafe_allow_html=True)
285
- txt = st.text_area("Describe symptoms...", height=100)
286
- if txt and st.button("Analyze Text"):
287
- input_data = txt
288
- input_type = "Text"
289
- st.markdown("</div>", unsafe_allow_html=True)
290
-
291
- # Tab 4: Face
292
- with t4:
293
- st.markdown("<div class='shinui-card'>", unsafe_allow_html=True)
294
- cam = st.camera_input("Face Scan")
295
- if cam:
296
- input_data = "Face Data"
297
- input_type = "Face Scan"
298
- # Trigger immediately on photo take if you prefer, or add button
299
- # We will trigger via button to be consistent
300
- if st.button("Analyze Face"):
301
- pass
302
- st.markdown("</div>", unsafe_allow_html=True)
303
-
304
- # PROCESSING LOGIC
305
- # We check if a button set input_data OR if cam was used
306
- if input_data and input_type:
307
- res = process_analysis(input_type, input_data)
308
- st.session_state.result = res
309
- # Add to history
310
- st.session_state.history.append({
311
- "date": res['date'],
312
- "summary": res['summary']
313
- })
314
- st.rerun() # Refresh to show results
315
-
316
- # RESULTS DISPLAY
317
- if st.session_state.result:
318
- r = st.session_state.result
319
- st.markdown("<br>", unsafe_allow_html=True)
320
- st.markdown(f"""
321
- <div class="shinui-card animate-slide-up" style="border-top: 4px solid #38bdf8;">
322
- <h3 style="margin-top:0;">Analysis Complete</h3>
323
- <p style="font-size:1.2rem; color: white;">{r['summary']}</p>
324
- <p style="color:#94a3b8;">{r['details']}</p>
325
- <hr style="border-color:rgba(255,255,255,0.1);">
326
- <div style="display:flex; justify-content:space-between;">
327
- <div><b>Risk:</b> {r['risk']}</div>
328
- <div><b>Action:</b> {r['action']}</div>
329
- </div>
330
- </div>
331
- """, unsafe_allow_html=True)
332
-
333
- if st.button("Clear Results"):
334
- st.session_state.result = None
335
- st.rerun()
336
-
337
- # -----------------------------------------------------------------------------
338
- # 6. ROUTER
339
- # -----------------------------------------------------------------------------
340
- if st.session_state.page == 'landing': show_landing()
341
- elif st.session_state.page == 'about': show_about()
342
- elif st.session_state.page == 'login': show_login()
343
- elif st.session_state.page == 'dashboard':
344
- if st.session_state.logged_in: show_dashboard()
345
- else: nav_to('login')
 
1
  import streamlit as st
2
+ import os
3
  import time
4
  from datetime import datetime
5
+ from PIL import Image
6
+ from huggingface_hub import InferenceClient
7
 
8
  # -----------------------------------------------------------------------------
9
+ # 1. SETUP & SECRETS
10
  # -----------------------------------------------------------------------------
11
+ st.set_page_config(page_title="SHINUI | Intelligent Care", page_icon="✨", layout="wide")
12
+
13
+ # AUTO-FIX FOR 403 ERROR
14
+ config_dir = ".streamlit"
15
+ if not os.path.exists(config_dir):
16
+ os.makedirs(config_dir)
17
+ with open(os.path.join(config_dir, "config.toml"), "w") as f:
18
+ f.write("[server]\nenableXsrfProtection=false\nenableCORS=false\nmaxUploadSize=200\n")
19
+
20
+ # RETRIEVE API KEY
21
+ HF_TOKEN = os.environ.get("HF_TOKEN")
22
+ if not HF_TOKEN:
23
+ st.error("⚠️ API Key missing! Please add 'HF_TOKEN' in Space Settings > Secrets.")
24
+ st.stop()
25
+
26
+ # INITIALIZE CLIENT
27
+ client = InferenceClient(token=HF_TOKEN)
28
 
29
  # -----------------------------------------------------------------------------
30
+ # 2. THE "UNGATED" BRAIN LOGIC
31
  # -----------------------------------------------------------------------------
32
+ def get_ai_insight(input_type, content):
33
+ """
34
+ Uses Open-Source, Non-Gated models to avoid permission errors.
35
+ """
36
+ # The "Brain" prompt that interprets the data
37
+ system_prompt = (
38
+ "You are SHINUI, an advanced medical AI. "
39
+ "Your goal is to analyze the user's input and provide clear, safe, and professional medical insights. "
40
+ "Structure your answer with: 'Analysis', 'Risk Level', and 'Recommended Action'. "
41
+ "Keep it concise and helpful."
42
+ )
43
+
44
+ try:
45
+ final_text_input = ""
46
+
47
+ # A. VISION (Uses BLIP - Ungated)
48
+ if input_type == "Image":
49
+ # 1. We use BLIP to "see" the image and describe it to text
50
+ # This model is open and requires no permissions.
51
+ image_description = client.image_to_text(
52
+ model="Salesforce/blip-image-captioning-large",
53
+ image=content
54
+ )
55
+ # 2. We send that description to the LLM to "medicalize" it
56
+ final_text_input = f"I have an image that shows: '{image_description}'. Please analyze this medically."
57
+
58
+ # B. AUDIO (Uses Whisper - Ungated)
59
+ elif input_type == "Audio":
60
+ # 1. Transcribe audio to text
61
+ transcription = client.automatic_speech_recognition(
62
+ model="openai/whisper-large-v3-turbo",
63
+ audio=content
64
+ ).text
65
+ final_text_input = f"The patient described their symptoms verbally: '{transcription}'. Analyze this."
66
+
67
+ # C. TEXT (Direct)
68
+ elif input_type == "Text":
69
+ final_text_input = f"Patient notes: '{content}'. Analyze this."
70
+
71
+ # D. FINAL REASONING (Uses Zephyr - Ungated & Smart)
72
+ # Zephyr-7b-beta is free, open, and very good at following instructions.
73
+ messages = [
74
+ {"role": "system", "content": system_prompt},
75
+ {"role": "user", "content": final_text_input}
76
+ ]
77
+
78
+ response = client.chat_completion(
79
+ model="HuggingFaceH4/zephyr-7b-beta",
80
+ messages=messages,
81
+ max_tokens=400
82
+ )
83
+
84
+ return response.choices[0].message.content
85
+
86
+ except Exception as e:
87
+ return f"⚠️ Analysis Error: {str(e)}"
88
 
89
  # -----------------------------------------------------------------------------
90
+ # 3. VISUAL STYLING (TIVE.COM STYLE)
91
  # -----------------------------------------------------------------------------
92
+ if 'history' not in st.session_state: st.session_state.history = []
93
+ if 'result' not in st.session_state: st.session_state.result = None
94
+
95
  st.markdown("""
96
  <style>
97
  @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;600;800&display=swap');
98
+
 
99
  .stApp {
100
  background-color: #020617;
101
  background-image: radial-gradient(circle at 50% 0%, #1e293b 0%, #020617 70%);
102
  font-family: 'Plus Jakarta Sans', sans-serif;
103
  color: #f8fafc;
104
  }
105
+
106
+ /* Card Styling */
 
 
 
 
 
 
 
 
 
107
  .shinui-card {
108
  background: rgba(30, 41, 59, 0.4);
109
  border: 1px solid rgba(148, 163, 184, 0.1);
110
  border-radius: 16px;
111
  padding: 25px;
112
  backdrop-filter: blur(12px);
 
113
  margin-bottom: 20px;
114
  }
115
+
116
+ /* Buttons */
 
 
 
 
 
 
117
  div.stButton > button {
118
+ background: #38bdf8;
119
+ color: #0f172a;
120
  border: none;
121
+ font-weight: 700;
122
+ padding: 12px 20px;
123
  border-radius: 8px;
 
124
  width: 100%;
125
+ transition: all 0.3s;
126
  }
127
  div.stButton > button:hover {
128
+ background: #ffffff;
129
+ box-shadow: 0 0 20px rgba(56, 189, 248, 0.5);
130
  }
131
 
132
+ /* Headers */
133
+ h1, h2, h3 { color: white; font-weight: 700; }
 
 
 
 
 
134
 
 
135
  #MainMenu, footer, header {visibility: hidden;}
136
  </style>
137
  """, unsafe_allow_html=True)
138
 
139
  # -----------------------------------------------------------------------------
140
+ # 4. APP UI
141
  # -----------------------------------------------------------------------------
142
+ c1, c2 = st.columns([1, 8])
143
+ with c1: st.markdown("### SHINUI")
144
+ st.markdown("<hr style='border-color:rgba(255,255,255,0.1)'>", unsafe_allow_html=True)
145
+
146
+ # Tabs
147
+ t1, t2, t3 = st.tabs(["📷 Vision", "🎙️ Voice", "📝 Text"])
148
+
149
+ # Tab 1: Vision
150
+ with t1:
151
+ st.markdown("<div class='shinui-card'>", unsafe_allow_html=True)
152
+ img_file = st.file_uploader("Upload Scan / Image", type=['png','jpg','jpeg'])
153
+ if img_file and st.button("Analyze Visual", key="btn_img"):
154
+ image = Image.open(img_file)
155
+ with st.spinner("Processing Visual Data..."):
156
+ res = get_ai_insight("Image", image)
157
+ st.session_state.result = res
158
+ st.session_state.history.append(res)
159
+ st.markdown("</div>", unsafe_allow_html=True)
160
+
161
+ # Tab 2: Voice
162
+ with t2:
163
+ st.markdown("<div class='shinui-card'>", unsafe_allow_html=True)
164
+ # Uses new Streamlit Audio Input (Record directly in browser)
165
+ audio_input = st.audio_input("Record Symptoms")
166
+ if audio_input:
167
+ if st.button("Analyze Recording", key="btn_audio"):
168
+ with st.spinner("Transcribing & Analyzing..."):
169
+ # Read audio bytes
170
+ res = get_ai_insight("Audio", audio_input.read())
171
+ st.session_state.result = res
172
+ st.session_state.history.append(res)
173
+ st.markdown("</div>", unsafe_allow_html=True)
174
+
175
+ # Tab 3: Text
176
+ with t3:
177
+ st.markdown("<div class='shinui-card'>", unsafe_allow_html=True)
178
+ txt = st.text_area("Clinical Notes / Symptoms")
179
+ if txt and st.button("Analyze Notes", key="btn_txt"):
180
+ with st.spinner("Analyzing..."):
181
+ res = get_ai_insight("Text", txt)
182
+ st.session_state.result = res
183
+ st.session_state.history.append(res)
184
+ st.markdown("</div>", unsafe_allow_html=True)
 
185
 
186
  # -----------------------------------------------------------------------------
187
+ # 5. RESULTS & HISTORY
188
  # -----------------------------------------------------------------------------
189
+ if st.session_state.result:
190
+ st.markdown(f"""
191
+ <div class='shinui-card' style='border-left: 5px solid #38bdf8;'>
192
+ <h3 style='margin-top:0; color:#38bdf8;'>Analysis Result</h3>
193
+ <div style='white-space: pre-wrap; color: #e2e8f0; line-height: 1.6;'>{st.session_state.result}</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  </div>
195
  """, unsafe_allow_html=True)
196
 
197
+ if st.session_state.history:
198
+ with st.expander("View Session History"):
199
+ for i, h in enumerate(reversed(st.session_state.history)):
200
+ st.markdown(f"**Scan {len(st.session_state.history)-i}:** {h[:100]}...")