ai / app.py
hassan773's picture
Update app.py
83233e7 verified
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
# --- 1. CORE SYSTEM CONFIG ---
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="πŸ₯")
# --- 2. THEME & PDF GENERATOR ---
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()
# --- 3. DATA PERSISTENCE ---
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
# --- 4. AUTHENTICATION ---
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()
# --- 5. SIDEBAR NAVIGATION ---
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"])
# --- 6. PATIENT PORTAL (STRICT MEDICAL AI) ---
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})
# --- STRICT MEDICAL GUARDRAIL ---
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)
# --- 7. DOCTOR PORTAL ---
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("|") # CALL|DocName|Room|Patient
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()