hassan773 commited on
Commit
a57d6f7
Β·
verified Β·
1 Parent(s): 4a2d3ec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -93
app.py CHANGED
@@ -6,66 +6,57 @@ import hashlib
6
  from datetime import datetime
7
  from groq import Groq
8
  import pdfplumber
 
9
 
10
- # --- 1. CONFIG ---
11
  GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
12
  if not GROQ_API_KEY:
13
- st.error("⚠️ API Key Missing!")
14
  st.stop()
15
 
16
- st.set_page_config(page_title="IntelliCare Portal", layout="wide")
17
 
18
- # --- 2. ADVANCED CSS: THE WIDE GEMINI INPUT BAR & FIXED SCROLL ---
19
- def inject_gemini_v2():
20
  st.markdown("""
21
  <style>
22
- /* 1. LOCK MAIN FRAME */
23
- .main { overflow: hidden; height: 100vh; }
24
-
25
- /* 2. GRADIENT SIDEBAR */
26
  [data-testid="stSidebar"] {
27
  background: linear-gradient(180deg, #0f172a 0%, #1e3a8a 100%);
28
  }
 
29
 
30
- /* 3. SCROLLABLE CHAT AREA */
31
- .chat-scroll-area {
32
- height: 70vh;
33
  overflow-y: auto;
34
- padding: 20px 5%;
35
  display: flex;
36
  flex-direction: column;
37
  }
38
 
39
- /* 4. LARGE UNIFIED INPUT BAR (FIXED BOTTOM) */
40
- /* We style the container to hold the Plus, Input, and Mic */
41
- .fixed-footer {
42
- position: fixed;
43
- bottom: 0;
44
- left: 0;
45
- width: 100%;
46
- background: white;
47
- padding: 20px 5%;
48
- z-index: 1000;
49
- border-top: 1px solid #e2e8f0;
50
  }
51
-
52
- /* Target Streamlit's internal input to make it wide and clean */
53
- div[data-testid="stChatInput"] {
54
- border-radius: 24px !important;
55
- border: 1px solid #dfe1e5 !important;
56
- box-shadow: 0 1px 6px rgba(32,33,36,0.28) !important;
57
  }
58
 
59
- /* 5. BUBBLES */
60
- .user-msg {
61
- background: #3b82f6; color: white; padding: 15px;
62
- border-radius: 18px 18px 0 18px; align-self: flex-end;
63
- margin-bottom: 15px; max-width: 80%;
64
  }
65
- .ai-msg {
66
- background: #ffffff; color: #1e293b; padding: 15px;
67
- border-radius: 18px 18px 18px 0; align-self: flex-start;
68
- margin-bottom: 15px; max-width: 80%; border: 1px solid #e2e8f0;
69
  }
70
  </style>
71
 
@@ -75,65 +66,121 @@ def inject_gemini_v2():
75
  const msg = new SpeechSynthesisUtterance(text);
76
  window.speechSynthesis.speak(msg);
77
  }
78
- const area = document.querySelector('.chat-scroll-area');
79
- if(area) { area.scrollTop = area.scrollHeight; }
80
  </script>
81
  """, unsafe_allow_html=True)
82
 
83
- inject_gemini_v2()
84
 
85
- # --- 3. SESSION STATE ---
 
 
 
 
 
 
 
86
  if "msgs" not in st.session_state: st.session_state.msgs = []
87
  if "active_doc" not in st.session_state: st.session_state.active_doc = None
88
 
89
- # --- 4. SIDEBAR (NAVIGATION ONLY) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  with st.sidebar:
91
- st.markdown("### πŸ‘€ Patient Portal")
92
- nav = st.radio("Go to", ["πŸ’¬ AI Chat", "πŸ§ͺ Health Lab", "πŸ“ Clinics"])
93
- if st.button("πŸšͺ Logout"): st.rerun()
94
-
95
- # --- 5. CHAT HISTORY (SCROLLABLE) ---
96
- st.markdown('<div class="chat-scroll-area">', unsafe_allow_html=True)
97
- for m in st.session_state.msgs:
98
- bubble = "user-msg" if m["role"] == "user" else "ai-msg"
99
- icon = f'<button style="border:none; background:none; cursor:pointer; float:right;" onclick="speak(\'{m["content"].replace("'", "")}\')">πŸ”Š</button>' if m["role"] == "assistant" else ""
100
- st.markdown(f'<div class="{bubble}">{icon}{m["content"]}</div>', unsafe_allow_html=True)
101
- st.markdown('</div>', unsafe_allow_html=True)
102
-
103
- # --- 6. THE GEMINI INPUT SUITE (Plus + Text + Mic) ---
104
- # We use columns to arrange them perfectly at the bottom
105
- st.divider()
106
- c1, c2, c3 = st.columns([1, 8, 1])
107
-
108
- with c1:
109
- with st.popover("βž•"):
110
- up_pdf = st.file_uploader("Document", type=['pdf'])
111
- if up_pdf:
112
- with pdfplumber.open(up_pdf) as f:
113
- st.session_state.active_doc = " ".join([p.extract_text() for p in f.pages if p.extract_text()])
114
- st.success("PDF Loaded")
115
-
116
- with c2:
117
- q = st.chat_input("Ask a medical question...")
118
-
119
- with c3:
120
- v = st.audio_input("🎀", label_visibility="collapsed")
121
-
122
- # --- 7. AI ENGINE ---
123
- final_q = q if q else (v if v else None)
124
-
125
- if final_q:
126
- if hasattr(final_q, 'getvalue'):
127
- final_q = Groq(api_key=GROQ_API_KEY).audio.transcriptions.create(file=("a.wav", final_q.getvalue()), model="whisper-large-v3", response_format="text")
128
-
129
- st.session_state.msgs.append({"role": "user", "content": final_q})
130
-
131
- sys_p = "You are a Medical Assistant. Be concise. If a PDF is uploaded, analyze it. Refuse non-medical queries."
132
- ctx = f"PDF_DATA: {st.session_state.active_doc}"
133
-
134
- ans = Groq(api_key=GROQ_API_KEY).chat.completions.create(
135
- model="llama-3.3-70b-versatile",
136
- messages=[{"role": "system", "content": sys_p}, {"role": "system", "content": ctx}] + st.session_state.msgs
137
- )
138
- st.session_state.msgs.append({"role": "assistant", "content": ans.choices[0].message.content})
139
- st.rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  from datetime import datetime
7
  from groq import Groq
8
  import pdfplumber
9
+ import plotly.graph_objects as go
10
 
11
+ # --- 1. CORE CONFIG ---
12
  GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
13
  if not GROQ_API_KEY:
14
+ st.error("⚠️ API Key Missing! Please set it in Space Secrets.")
15
  st.stop()
16
 
17
+ st.set_page_config(page_title="IntelliCare Portal | Hassan Naseer", layout="wide", page_icon="πŸ₯")
18
 
19
+ # --- 2. ADVANCED CSS: GEMINI UI + SCROLL LOCK ---
20
+ def inject_full_design():
21
  st.markdown("""
22
  <style>
23
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap');
24
+ html, body, [class*="css"] { font-family: 'Poppins', sans-serif; overflow: hidden; height: 100vh; }
25
+
26
+ /* Gradient Sidebar (Static) */
27
  [data-testid="stSidebar"] {
28
  background: linear-gradient(180deg, #0f172a 0%, #1e3a8a 100%);
29
  }
30
+ [data-testid="stSidebar"] * { color: white !important; }
31
 
32
+ /* Scrollable Chat Area Only */
33
+ .chat-history-box {
34
+ height: 60vh;
35
  overflow-y: auto;
36
+ padding: 20px 2%;
37
  display: flex;
38
  flex-direction: column;
39
  }
40
 
41
+ /* Gemini Bubbles */
42
+ .user-msg {
43
+ background: #3b82f6; color: white; padding: 15px; border-radius: 20px 20px 0 20px;
44
+ align-self: flex-end; margin-bottom: 12px; max-width: 80%;
 
 
 
 
 
 
 
45
  }
46
+ .ai-msg {
47
+ background: #ffffff; color: #1e293b; padding: 15px; border-radius: 20px 20px 20px 0;
48
+ align-self: flex-start; margin-bottom: 12px; max-width: 80%; border: 1px solid #e2e8f0;
 
 
 
49
  }
50
 
51
+ /* Fixed Bottom Prompt Bar */
52
+ .fixed-input-bar {
53
+ position: fixed; bottom: 20px; left: 18%; width: 80%;
54
+ background: white; padding: 10px; z-index: 1000;
 
55
  }
56
+
57
+ /* Widening the Prompt Box */
58
+ div[data-testid="stChatInput"] {
59
+ border-radius: 25px !important; border: 1px solid #3b82f6 !important;
60
  }
61
  </style>
62
 
 
66
  const msg = new SpeechSynthesisUtterance(text);
67
  window.speechSynthesis.speak(msg);
68
  }
 
 
69
  </script>
70
  """, unsafe_allow_html=True)
71
 
72
+ inject_full_design()
73
 
74
+ # --- 3. DATABASE & SESSION ---
75
+ USER_DB, HISTORY_DB = "users_secure.csv", "clinical_history.csv"
76
+ def hash_pass(pwd): return hashlib.sha256(str.encode(pwd)).hexdigest()
77
+ def load_db(file, cols):
78
+ if os.path.exists(file): return pd.read_csv(file)
79
+ return pd.DataFrame(columns=cols)
80
+
81
+ if "logged_in" not in st.session_state: st.session_state.logged_in = False
82
  if "msgs" not in st.session_state: st.session_state.msgs = []
83
  if "active_doc" not in st.session_state: st.session_state.active_doc = None
84
 
85
+ # --- 4. AUTHENTICATION PORTAL (Account Creation & Login) ---
86
+
87
+ if not st.session_state.logged_in:
88
+ st.markdown("<h1 style='text-align: center; color: #1e3a8a;'>πŸ₯ INTELLICARE PORTAL</h1>", unsafe_allow_html=True)
89
+ c1, c2, c3 = st.columns([1, 2, 1])
90
+ with c2:
91
+ tab1, tab2 = st.tabs(["πŸ” Login", "✨ Create Account"])
92
+ with tab1:
93
+ u, p = st.text_input("Username"), st.text_input("Password", type="password")
94
+ if st.button("SIGN IN"):
95
+ users = load_db(USER_DB, ["username", "password", "role"])
96
+ match = users[(users['username'] == u) & (users['password'] == hash_pass(p))]
97
+ if not match.empty:
98
+ st.session_state.logged_in, st.session_state.username = True, u
99
+ st.session_state.role = match.iloc[0]['role']
100
+ st.rerun()
101
+ with tab2:
102
+ nu, np, nr = st.text_input("New ID"), st.text_input("New Pass", type="password"), st.selectbox("Role", ["Patient", "Doctor"])
103
+ if st.button("REGISTER"):
104
+ df = load_db(USER_DB, ["username", "password", "role"])
105
+ if nu not in df['username'].values:
106
+ pd.concat([df, pd.DataFrame([{"username": nu, "password": hash_pass(np), "role": nr}])]).to_csv(USER_DB, index=False)
107
+ st.success("Account Created!")
108
+ st.stop()
109
+
110
+ # --- 5. DUAL-PORTAL NAVIGATION ---
111
  with st.sidebar:
112
+ st.markdown(f"### πŸ‘€ {st.session_state.username}\n**{st.session_state.role} Portal**")
113
+ if st.button("πŸšͺ Logout"): st.session_state.logged_in = False; st.rerun()
114
+ st.divider()
115
+ if st.session_state.role == "Patient":
116
+ nav = st.radio("Navigation", ["πŸ’¬ Medical AI", "πŸ§ͺ Health Lab", "πŸ“ Clinics"])
117
+ else:
118
+ nav = st.radio("Navigation", ["πŸ–₯️ Consultation Desk", "πŸ“‹ Patient Records"])
119
+
120
+ # --- 6. PATIENT PORTAL ---
121
+ if st.session_state.role == "Patient":
122
+ if nav == "πŸ’¬ Medical AI":
123
+ st.markdown("### πŸ’¬ Clinical Intelligence")
124
+ if st.session_state.active_doc: st.info("πŸ“„ Document Context Loaded")
125
+
126
+ # SCROLLABLE CHAT AREA
127
+ st.markdown('<div class="chat-history-box">', unsafe_allow_html=True)
128
+ for m in st.session_state.msgs:
129
+ bubble = "user-msg" if m["role"] == "user" else "ai-msg"
130
+ icon = f'<button style="float:right; border:none; background:none; cursor:pointer;" onclick="speak(\'{m["content"].replace("'", "")}\')">πŸ”Š</button>' if m["role"] == "assistant" else ""
131
+ st.markdown(f'<div class="{bubble}">{icon}{m["content"]}</div>', unsafe_allow_html=True)
132
+ st.markdown('</div>', unsafe_allow_html=True)
133
+
134
+ # GEMINI UNIFIED INPUT (Bottom of screen)
135
+ st.divider()
136
+ prompt_col1, prompt_col2, prompt_col3 = st.columns([1, 8, 1])
137
+ with prompt_col1:
138
+ with st.popover("βž•"):
139
+ up_pdf = st.file_uploader("PDF", type=['pdf'])
140
+ if up_pdf:
141
+ with pdfplumber.open(up_pdf) as f:
142
+ st.session_state.active_doc = " ".join([p.extract_text() for p in f.pages if p.extract_text()])
143
+ st.success("Analyzed!")
144
+ with prompt_col2:
145
+ q = st.chat_input("Ask about symptoms or reports...")
146
+ with prompt_col3:
147
+ v = st.audio_input("🎀", label_visibility="collapsed")
148
+
149
+ # AI LOGIC
150
+ final_q = q if q else (v if v else None)
151
+ if final_q:
152
+ if hasattr(final_q, 'getvalue'):
153
+ final_q = Groq(api_key=GROQ_API_KEY).audio.transcriptions.create(file=("a.wav", final_q.getvalue()), model="whisper-large-v3", response_format="text")
154
+ st.session_state.msgs.append({"role": "user", "content": final_q})
155
+ sys_p = "You are a Medical AI. Strictly medical only. Analyze PDFs if present."
156
+ ans = Groq(api_key=GROQ_API_KEY).chat.completions.create(model="llama-3.3-70b-versatile", messages=[{"role": "system", "content": sys_p}, {"role": "system", "content": f"CTX: {st.session_state.active_doc}"}] + st.session_state.msgs)
157
+ st.session_state.msgs.append({"role": "assistant", "content": ans.choices[0].message.content})
158
+ st.rerun()
159
+
160
+ elif nav == "πŸ§ͺ Health Lab":
161
+ st.markdown("### πŸ§ͺ Diagnostics Library")
162
+ tool = st.selectbox("Tool", ["BMI Analyzer", "Glucose Tracker", "Heart Rate"])
163
+ if tool == "BMI Analyzer":
164
+
165
+ w, h = st.number_input("Weight (kg)", 30, 200, 70), st.number_input("Height (cm)", 100, 250, 175)
166
+ bmi = round(w / ((h/100)**2), 1)
167
+ st.plotly_chart(go.Figure(go.Indicator(mode="gauge+number", value=bmi, gauge={'bar': {'color': "#10b981"}})))
168
+ elif tool == "Glucose Tracker":
169
+
170
+ st.area_chart(pd.DataFrame(np.random.randn(20, 1).cumsum() + 100))
171
+ elif tool == "Heart Rate":
172
+
173
+ hr = st.slider("BPM", 40, 180, 72)
174
+ t = np.linspace(0, 2, 200); y = np.sin(2 * np.pi * (hr/60) * t)
175
+ st.plotly_chart(go.Figure(data=go.Scatter(y=y, mode='lines', line=dict(color='#ff4b4b'))))
176
+
177
+ # --- 7. DOCTOR PORTAL ---
178
+ elif st.session_state.role == "Doctor":
179
+ if nav == "πŸ–₯️ Consultation Desk":
180
+ st.markdown("### πŸ–₯️ Active Consultations")
181
+ room_id = st.text_input("Enter Patient Room ID")
182
+ if st.button("JOIN CALL"):
183
+ st.components.v1.html(f'<iframe src="https://meet.jit.si/{room_id}" width="100%" height="600px"></iframe>', height=650)
184
+ elif nav == "πŸ“‹ Patient Records":
185
+ st.markdown("### πŸ“‹ Records Archive")
186
+ st.dataframe(load_db(HISTORY_DB, ["Time", "Patient", "Status"]), use_container_width=True)