import streamlit as st import os import time import re import requests import json from PIL import Image from io import BytesIO from openai import OpenAI # ------------------ Authentication ------------------ VALID_USERS = { "andrew@lortechnologies.com": "Pass.123", "asherS@schlagergroup.com.au": "Pass.123", "daniel@schlagergroup.com.au": "Pass.123", "admin@schlagergroup.com.au": "Pass.123", } def login(): st.title("๐Ÿ” Login Required") email = st.text_input("Email") password = st.text_input("Password", type="password") if st.button("Login"): if VALID_USERS.get(email) == password: st.session_state.authenticated = True st.rerun() else: st.error("โŒ Incorrect email or password.") if "authenticated" not in st.session_state: st.session_state.authenticated = False if not st.session_state.authenticated: login() st.stop() # ------------------ App Config ------------------ st.set_page_config(page_title="AI Pathology Assistant", layout="wide", initial_sidebar_state="collapsed") st.title("๐Ÿงฌ AI Pathology Assistant") # ------------------ Load OpenAI ------------------ OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") if not OPENAI_API_KEY: st.error("โŒ Missing OPENAI_API_KEY environment variable.") st.stop() client = OpenAI(api_key=OPENAI_API_KEY) # ------------------ Assistant Setup ------------------ ASSISTANT_ID = "asst_9v09zgizdcuuhNdcFQpRo9RO" # ------------------ State ------------------ if "messages" not in st.session_state: st.session_state.messages = [] if "thread_id" not in st.session_state: st.session_state.thread_id = None if "image_urls" not in st.session_state: st.session_state.image_urls = [] if "pending_prompt" not in st.session_state: st.session_state.pending_prompt = None # ------------------ Tabs ------------------ tab1, tab2 = st.tabs(["๐Ÿ’ฌ Chat Assistant", "๐Ÿ–ผ๏ธ Visual Reference Search"]) # ------------------ Tab 1: Chat Assistant ------------------ with tab1: with st.sidebar: st.header("๐Ÿงช Pathology Tools") if st.button("๐Ÿงน Clear Chat"): st.session_state.messages = [] st.session_state.thread_id = None st.session_state.image_urls = [] st.session_state.pending_prompt = None st.rerun() show_image = st.toggle("๐Ÿ“ธ Show Images", value=True) keyword = st.text_input("Keyword Search", placeholder="e.g. mitosis, carcinoma") if st.button("๐Ÿ”Ž Search") and keyword: st.session_state.pending_prompt = f"Find clauses or references related to: {keyword}" section = st.text_input("Section Lookup", placeholder="e.g. Connective Tissue") if section: st.session_state.pending_prompt = f"Summarize or list key points from section: {section}" actions = [ "Select an action...", "List histological features of inflammation", "Summarize features of carcinoma", "List muscle types and features", "Extract diagnostic markers", "Summarize embryology stages" ] action = st.selectbox("Common Pathology Queries", actions) if action != actions[0]: st.session_state.pending_prompt = action chat_col, image_col = st.columns([2, 1]) with chat_col: st.markdown("### ๐Ÿ’ฌ Ask a Pathology-Specific Question") user_input = st.chat_input("Example: What are features of squamous cell carcinoma?") if user_input: st.session_state.messages.append({"role": "user", "content": user_input}) elif st.session_state.pending_prompt: st.session_state.messages.append({"role": "user", "content": st.session_state.pending_prompt}) st.session_state.pending_prompt = None if st.session_state.messages and st.session_state.messages[-1]["role"] == "user": try: if st.session_state.thread_id is None: thread = client.beta.threads.create() st.session_state.thread_id = thread.id client.beta.threads.messages.create( thread_id=st.session_state.thread_id, role="user", content=st.session_state.messages[-1]["content"] ) run = client.beta.threads.runs.create( thread_id=st.session_state.thread_id, assistant_id=ASSISTANT_ID ) with st.spinner("๐Ÿ”ฌ Analyzing..."): while True: status = client.beta.threads.runs.retrieve(thread_id=st.session_state.thread_id, run_id=run.id) if status.status in ("completed", "failed", "cancelled"): break time.sleep(1) if status.status == "completed": messages = client.beta.threads.messages.list(thread_id=st.session_state.thread_id) for m in reversed(messages.data): if m.role == "assistant": reply = m.content[0].text.value st.session_state.messages.append({"role": "assistant", "content": reply}) image_matches = re.findall( r'https://raw\.githubusercontent\.com/AndrewLORTech/witspathologai/main/[^\s\n"]+\.png', reply ) st.session_state.image_urls = image_matches break else: st.error("โŒ Assistant failed to respond.") st.rerun() except Exception as e: st.error(f"โŒ Error: {e}") for msg in st.session_state.messages: with st.chat_message(msg["role"]): st.markdown(msg["content"], unsafe_allow_html=True) with image_col: if show_image and st.session_state.image_urls: st.markdown("### ๐Ÿ–ผ๏ธ Image(s)") for raw_url in st.session_state.image_urls: try: r = requests.get(raw_url, timeout=5) r.raise_for_status() img = Image.open(BytesIO(r.content)) st.image(img, caption=f"๐Ÿ“ท {raw_url.split('/')[-1]}", use_container_width=True) except Exception: continue # ------------------ Tab 2: Visual Reference Search ------------------ with tab2: ASSISTANT_ID = "asst_9v09zgizdcuuhNdcFQpRo9RO" # Your visual reference assistant ID # Session states if "image_thread_id" not in st.session_state: st.session_state.image_thread_id = None if "image_response" not in st.session_state: st.session_state.image_response = None if "image_results" not in st.session_state: st.session_state.image_results = [] if "image_lightbox" not in st.session_state: st.session_state.image_lightbox = None # User input image_input = st.chat_input("Ask for histology visual references (e.g. ovary histology, mitosis)") if image_input: st.session_state.image_response = None st.session_state.image_results = [] st.session_state.image_lightbox = None try: if st.session_state.image_thread_id is None: thread = client.beta.threads.create() st.session_state.image_thread_id = thread.id client.beta.threads.messages.create( thread_id=st.session_state.image_thread_id, role="user", content=image_input ) run = client.beta.threads.runs.create( thread_id=st.session_state.image_thread_id, assistant_id=ASSISTANT_ID ) with st.spinner("๐Ÿ”ฌ Searching for histology references..."): while True: run_status = client.beta.threads.runs.retrieve( thread_id=st.session_state.image_thread_id, run_id=run.id ) if run_status.status in ("completed", "failed", "cancelled"): break time.sleep(1) if run_status.status == "completed": messages = client.beta.threads.messages.list(thread_id=st.session_state.image_thread_id) for msg in reversed(messages.data): if msg.role == "assistant": response_text = msg.content[0].text.value st.session_state.image_response = response_text # ๐Ÿ” Robustly extract "Image URL" entries lines = response_text.splitlines() image_urls = [] for i, line in enumerate(lines): if "Image URL:" in line: url = line.split("Image URL:")[-1].strip() if not url.startswith("http") and i + 1 < len(lines): next_line = lines[i + 1].strip() if next_line.startswith("http"): url = next_line if url.startswith("http"): image_urls.append(url) st.session_state.image_results = [{"image": url} for url in image_urls] break except Exception as e: st.error(f"โŒ Visual Assistant Error: {e}") # Layout split text_col, image_col = st.columns([2, 1]) with text_col: if st.session_state.image_response: st.markdown("### ๐Ÿง  Assistant Response") st.markdown(st.session_state.image_response, unsafe_allow_html=True) with image_col: if st.session_state.image_results: st.markdown("### ๐Ÿ–ผ๏ธ Image Preview(s)") for i, item in enumerate(st.session_state.image_results): image_url = item.get("image") if image_url: try: st.image(image_url, caption=image_url.split("/")[-1], use_container_width=True) if st.button("๐Ÿ–ผ๏ธ View Full Image", key=f"img_{i}"): st.session_state.image_lightbox = image_url except Exception as e: st.warning(f"โš ๏ธ Could not load: {image_url}") st.error(f"{e}") else: st.info("โ„น๏ธ No image references found yet.") if st.session_state.image_lightbox: st.image(st.session_state.image_lightbox, caption="๐Ÿ” Full Image View", use_container_width=True) if st.button("โŒ Close Preview"): st.session_state.image_lightbox = None st.rerun()