| import streamlit as st |
| import pandas as pd |
| import numpy as np |
| import os |
| import hashlib |
| import requests |
| from datetime import datetime |
| from PIL import Image |
| from fpdf import FPDF |
| import plotly.graph_objects as go |
| import google.generativeai as genai |
| import folium |
| from streamlit_folium import st_folium |
| from streamlit_geolocation import streamlit_geolocation |
| from groq import Groq |
|
|
| |
| GROQ_API_KEY = os.environ.get("GROQ_API_KEY") |
| GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") |
|
|
| if not GROQ_API_KEY or not GOOGLE_API_KEY: |
| st.error("β οΈ API Keys Missing! Please set them in Space Secrets.") |
| st.stop() |
|
|
| genai.configure(api_key=GOOGLE_API_KEY) |
| vision_model = genai.GenerativeModel('gemini-2.0-flash') |
|
|
| st.set_page_config(page_title="IntelliCare Portal | Hassan Naseer", layout="wide", page_icon="π₯") |
|
|
| |
| if "theme" not in st.session_state: st.session_state.theme = "Light" |
|
|
| def generate_medical_pdf(chat_history, username): |
| pdf = FPDF() |
| pdf.add_page() |
| pdf.set_font("Arial", 'B', 16) |
| pdf.cell(200, 10, txt="IntelliCare Portal - Clinical Summary", ln=True, align='C') |
| pdf.set_font("Arial", size=10) |
| pdf.cell(200, 10, txt=f"Patient: {username} | Date: {datetime.now().strftime('%Y-%m-%d %H:%M')}", ln=True, align='C') |
| pdf.ln(10) |
| for msg in chat_history: |
| role = "PATIENT" if msg["role"] == "user" else "CLINICAL AI" |
| pdf.set_font("Arial", 'B', 10) |
| pdf.cell(0, 10, txt=f"{role}:", ln=True) |
| pdf.set_font("Arial", size=10) |
| pdf.multi_cell(0, 8, txt=msg["content"]) |
| pdf.ln(4) |
| pdf.ln(10) |
| pdf.set_font("Arial", 'I', 8) |
| pdf.multi_cell(0, 5, txt="DISCLAIMER: This is an AI-generated summary for information purposes only. Consult a human professional for definitive diagnosis.") |
| return pdf.output(dest='S').encode('latin-1') |
|
|
| def inject_theme(): |
| bg = "#000000" if st.session_state.theme == "Dark" else "#FFFFFF" |
| txt = "#FFFFFF" if st.session_state.theme == "Dark" else "#000000" |
| card = "#111111" if st.session_state.theme == "Dark" else "#F0F2F6" |
| brd = "#FFFFFF" if st.session_state.theme == "Dark" else "#000000" |
| st.markdown(f"""<style>.stApp {{ background-color: {bg} !important; color: {txt} !important; }} |
| div[data-baseweb="input"], div[data-baseweb="textarea"], select {{ border: 2px solid {brd} !important; border-radius: 10px !important; }} |
| .chat-bubble {{ padding: 15px; border-radius: 15px; margin-bottom: 10px; border: 1px solid {brd}; }} |
| .user-msg {{ background-color: rgba(59, 130, 246, 0.1); border-left: 5px solid #3b82f6; }} |
| .ai-msg {{ background-color: rgba(16, 185, 129, 0.1); border-left: 5px solid #10b981; }} |
| .clinical-card {{ background-color: {card}; border: 2px solid {brd}; padding: 25px; border-radius: 15px; color: {txt}; }} |
| [data-testid="stSidebar"] * {{ color: {txt} !important; }}</style>""", unsafe_allow_html=True) |
|
|
| inject_theme() |
|
|
| |
| USER_DB, HISTORY_DB = "users_secure.csv", "clinical_history.csv" |
| def hash_pass(pwd): return hashlib.sha256(str.encode(pwd)).hexdigest() |
| def load_db(file, cols): |
| if os.path.exists(file): return pd.read_csv(file) |
| return pd.DataFrame(columns=cols) |
|
|
| def get_vector_db(): |
| import chromadb |
| from chromadb.utils import embedding_functions |
| client = chromadb.PersistentClient(path="./med_vector_db") |
| emb_fn = embedding_functions.SentenceTransformerEmbeddingFunction(model_name="paraphrase-MiniLM-L3-v2") |
| main = client.get_or_create_collection(name="intellicare_vFinal", embedding_function=emb_fn) |
| signal = client.get_or_create_collection(name="signals_targeted", embedding_function=emb_fn) |
| return main, signal |
|
|
| |
| if "logged_in" not in st.session_state: st.session_state.logged_in = False |
| if "last_processed_audio" not in st.session_state: st.session_state.last_processed_audio = None |
|
|
| if not st.session_state.logged_in: |
| st.markdown("<h1 style='text-align: center;'>π₯ IntelliCare Portal</h1>", unsafe_allow_html=True) |
| c2 = st.columns([1, 2, 1])[1] |
| with c2: |
| tab1, tab2 = st.tabs(["π Login", "π Create Account"]) |
| with tab1: |
| u, p = st.text_input("Username", key="l_u"), st.text_input("Password", type="password", key="l_p") |
| if st.button("Sign In"): |
| users = load_db(USER_DB, ["username", "password", "role"]) |
| match = users[(users['username'] == u) & (users['password'] == hash_pass(p))] |
| if not match.empty: |
| st.session_state.logged_in, st.session_state.username = True, u |
| st.session_state.role, st.session_state.msgs = match.iloc[0]['role'], [] |
| st.rerun() |
| with tab2: |
| nu, np, nr = st.text_input("New User"), st.text_input("New Pass", type="password"), st.selectbox("Role", ["Patient", "Doctor"]) |
| if st.button("Register"): |
| df = load_db(USER_DB, ["username", "password", "role"]) |
| if nu not in df['username'].values: |
| pd.concat([df, pd.DataFrame([{"username": nu, "password": hash_pass(np), "role": nr}])]).to_csv(USER_DB, index=False) |
| st.success("β
Registered!") |
| st.stop() |
|
|
| |
| with st.sidebar: |
| st.markdown(f"### π€ {st.session_state.username} ({st.session_state.role})") |
| if st.button("π Toggle Theme"): |
| st.session_state.theme = "Dark" if st.session_state.theme == "Light" else "Light" |
| st.rerun() |
| if st.button("Logout"): st.session_state.logged_in = False; st.rerun() |
| st.divider() |
| nav = st.radio("Menu", ["π¬ AI Chat", "π§ͺ Health Lab", "π Nearby Hospitals", "π Video Call", "π History"]) if st.session_state.role == "Patient" else st.radio("Menu", ["π₯οΈ Consultation Desk", "π Patient Records"]) |
|
|
| |
| if st.session_state.role == "Patient": |
| if nav == "π¬ AI Chat": |
| st.markdown('<div class="clinical-card"><h3>π¬ Clinical AI Specialist</h3></div>', unsafe_allow_html=True) |
| for m in st.session_state.msgs: |
| bubble = "user-msg" if m["role"] == "user" else "ai-msg" |
| st.markdown(f'<div class="chat-bubble {bubble}"><b>{m["role"].upper()}:</b><br>{m["content"]}</div>', unsafe_allow_html=True) |
| |
| q = st.chat_input("Explain symptoms...") |
| c1, c2, c3 = st.columns(3) |
| with c1: v = st.audio_input("π€ Voice", key=f"v_{len(st.session_state.msgs)}") |
| with c2: up_pdf = st.file_uploader("π PDF", type=['pdf'], key=f"p_{len(st.session_state.msgs)}") |
| with c3: up_img = st.file_uploader("πΈ Scan", type=['png', 'jpg'], key=f"i_{len(st.session_state.msgs)}") |
| |
| final_q = q if q else None |
| if up_pdf: |
| import pdfplumber |
| with pdfplumber.open(up_pdf) as f: final_q = "Analyze these medical records: " + " ".join([p.extract_text() for p in f.pages if p.extract_text()]) |
| elif up_img: |
| res = vision_model.generate_content(["Extract clinical data from this medical image:", Image.open(up_img)]) |
| final_q = "Medical Image Data: " + res.text |
| elif v: |
| v_hash = hashlib.md5(v.getvalue()).hexdigest() |
| if v_hash != st.session_state.last_processed_audio: |
| final_q = Groq(api_key=GROQ_API_KEY).audio.transcriptions.create(file=("a.wav", v.getvalue()), model="whisper-large-v3", response_format="text") |
| st.session_state.last_processed_audio = v_hash |
|
|
| if final_q: |
| st.session_state.msgs.append({"role": "user", "content": final_q}) |
| |
| sys_prompt = "You are a specialized Clinical AI. Only answer medical, health, and pharmacological questions. Politely decline non-medical topics. Be precise and ground answers in provided context." |
| main_col, _ = get_vector_db() |
| res = main_col.query(query_texts=[final_q], n_results=1) |
| ctx = res['documents'][0][0] if (res.get('documents') and len(res['documents'][0]) > 0) else "N/A" |
| ans = Groq(api_key=GROQ_API_KEY).chat.completions.create(model="llama-3.3-70b-versatile", messages=[{"role": "system", "content": sys_prompt}, {"role": "system", "content": f"Context: {ctx}"}] + st.session_state.msgs) |
| st.session_state.msgs.append({"role": "assistant", "content": ans.choices[0].message.content}) |
| st.rerun() |
|
|
| if st.session_state.msgs: |
| if st.download_button("π₯ Download Summary (PDF)", generate_medical_pdf(st.session_state.msgs, st.session_state.username), f"IntelliCare_Summary_{st.session_state.username}.pdf", "application/pdf"): |
| st.success("Summary Generated!") |
|
|
| elif nav == "π§ͺ Health Lab": |
| st.markdown('<div class="clinical-card"><h3>π§ͺ Clinical Diagnostics</h3></div>', unsafe_allow_html=True) |
| col1, col2 = st.columns(2) |
| with col1: |
| w, h_cm = st.number_input("Weight (kg)", 30, 200, 70), st.number_input("Height (cm)", 100, 250, 175) |
| bmi = round(w / ((h_cm/100)**2), 1) |
| fig_bmi = go.Figure(go.Indicator(mode="gauge+number", value=bmi, domain={'x': [0, 1], 'y': [0, 1]}, gauge={'axis': {'range': [10, 40]}, 'bar': {'color': "#10b981"}, 'steps': [{'range': [10, 18.5], 'color': "lightblue"}, {'range': [25, 40], 'color': "orange"}]})) |
| st.plotly_chart(fig_bmi, use_container_width=True) |
| with col2: |
| hr = st.slider("Heart Rate (BPM)", 40, 180, 72) |
| t = np.linspace(0, 2, 200) |
| y = np.sin(2 * np.pi * (hr/60) * t) + 0.5 * np.sin(4 * np.pi * (hr/60) * t) |
| st.plotly_chart(go.Figure(data=go.Scatter(y=y, mode='lines', line=dict(color='#ff4b4b', width=3))), use_container_width=True) |
| |
|
|
| elif nav == "π Nearby Hospitals": |
| loc = streamlit_geolocation() |
| if loc and loc.get("latitude"): |
| lat, lon = loc["latitude"], loc["longitude"] |
| query = f'[out:json];node["amenity"~"hospital|clinic"](around:5000,{lat},{lon});out body;' |
| data = requests.get("http://overpass-api.de/api/interpreter", params={'data': query}).json() |
| m = folium.Map(location=[lat, lon], zoom_start=14) |
| folium.Marker([lat, lon], popup="You", icon=folium.Icon(color="blue")).add_to(m) |
| for e in data.get('elements', []): folium.Marker([e['lat'], e['lon']], popup=e.get('tags', {}).get('name', 'Clinic'), icon=folium.Icon(color="red")).add_to(m) |
| st_folium(m, width=1200, height=500) |
| else: st.info("Enable location to scan local clinics.") |
|
|
| elif nav == "π Video Call": |
| docs = load_db(USER_DB, ["username", "role"]) |
| sel = st.selectbox("Select Doctor", docs[docs['role'] == 'Doctor']['username'].tolist()) |
| if st.button("Request Connection"): |
| room = f"IntelliCare-{st.session_state.username}-{sel}" |
| _, signal_col = get_vector_db() |
| signal_col.upsert(documents=[f"CALL|{sel}|{room}|{st.session_state.username}"], ids=["latest"]) |
| st.session_state.active_room = room |
| if "active_room" in st.session_state: |
| st.components.v1.html(f'<iframe src="https://meet.jit.si/{st.session_state.active_room}" width="100%" height="550px"></iframe>', height=600) |
|
|
| |
| elif st.session_state.role == "Doctor": |
| if nav == "π₯οΈ Consultation Desk": |
| st.markdown('<div class="clinical-card"><h3>π₯οΈ Consultation Desk</h3></div>', unsafe_allow_html=True) |
| _, signal_col = get_vector_db() |
| res = signal_col.get(ids=["latest"]) |
| if res.get('documents'): |
| parts = res['documents'][0].split("|") |
| if parts[1] == st.session_state.username: |
| st.warning(f"π {parts[3]} is requesting a call.") |
| if st.button("β
Join Call"): st.session_state.active_call = parts[2] |
| if "active_call" in st.session_state: |
| st.components.v1.html(f'<iframe src="https://meet.jit.si/{parts[2]}" width="100%" height="600px"></iframe>', height=650) |
| if st.button("π΄ End & Archive"): |
| df = load_db(HISTORY_DB, ["Time", "Patient", "Doctor", "Status"]) |
| pd.concat([df, pd.DataFrame([{"Time": datetime.now().strftime("%Y-%m-%d %H:%M"), "Patient": parts[3], "Doctor": st.session_state.username, "Status": "Completed"}])]).to_csv(HISTORY_DB, index=False) |
| signal_col.delete(ids=["latest"]); del st.session_state.active_call; st.rerun() |