| import streamlit as st
|
| from PIL import Image
|
| import tensorflow as tf
|
| import google.generativeai as genai
|
| import numpy as np
|
| import os
|
| import time
|
| import requests
|
| from bs4 import BeautifulSoup
|
| import faiss
|
| import pickle
|
| import json
|
| import logging
|
| from typing import Dict, Any, Tuple
|
| import sys
|
| import dotenv
|
|
|
|
|
| logging.basicConfig(
|
| level=logging.INFO,
|
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| )
|
| logger = logging.getLogger('bloodcell_app')
|
|
|
|
|
| dotenv.load_dotenv()
|
|
|
|
|
| st.set_page_config(
|
| page_title="AI Chat & Blood Classifier",
|
| layout="wide",
|
| initial_sidebar_state="auto"
|
| )
|
|
|
|
|
| try:
|
|
|
| from AgentPro.agentpro import AgentPro
|
| from AgentPro.agentpro.tools import AresInternetTool
|
|
|
|
|
| tools = [AresInternetTool()]
|
| agent = AgentPro(tools=tools)
|
| logger.info("AgentPro initialized successfully with AresInternetTool")
|
| hospital_search_available = True
|
| except ImportError:
|
| try:
|
| sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "AgentPro"))
|
| from agentpro import AgentPro
|
| from agentpro.tools import AresInternetTool
|
|
|
|
|
| tools = [AresInternetTool()]
|
| agent = AgentPro(tools=tools)
|
| logger.info("AgentPro initialized successfully with AresInternetTool")
|
| hospital_search_available = True
|
| except ImportError:
|
| logger.warning("Unable to import AgentPro. Hospital search functionality will be disabled.")
|
|
|
| class AgentProPlaceholder:
|
| def __init__(self, *args, **kwargs):
|
| pass
|
| def __call__(self, *args, **kwargs):
|
| return "AgentPro is not available. Please check your installation."
|
|
|
| class AresInternetToolPlaceholder:
|
| def __init__(self):
|
| self.name = "AresInternetTool"
|
| self.description = "A tool for searching the internet (currently unavailable)"
|
|
|
| AgentPro = AgentProPlaceholder
|
| AresInternetTool = AresInternetToolPlaceholder
|
| agent = None
|
| hospital_search_available = False
|
| except Exception as e:
|
| logger.error(f"Error initializing AgentPro: {e}")
|
| agent = None
|
| hospital_search_available = False
|
|
|
|
|
|
|
| HOSPITAL_CACHE_DURATION = 86400
|
| HOSPITAL_CACHE = {}
|
|
|
| def search_hospitals(agent, disease: str, location: str, force_refresh: bool = False) -> str:
|
| """
|
| Search for hospitals that specialize in treating a specific blood disease in a given location.
|
|
|
| Args:
|
| agent: The AgentPro instance to use for search
|
| disease: The blood disease or indicator (e.g., "NPM1", "PML_RARA")
|
| location: The city/location to search in (e.g., "Lahore")
|
| force_refresh: Whether to force a fresh search, ignoring cache
|
|
|
| Returns:
|
| str: A formatted response with hospital information
|
| """
|
|
|
| cache_key = (disease, location)
|
| if not force_refresh and cache_key in HOSPITAL_CACHE:
|
| timestamp, results = HOSPITAL_CACHE[cache_key]
|
|
|
| if time.time() - timestamp < HOSPITAL_CACHE_DURATION:
|
| logger.info(f"Using cached results for {disease} in {location}")
|
| return results
|
|
|
| try:
|
|
|
| if disease in ["NPM1", "PML_RARA", "RUNX1_RUNX1T1"]:
|
|
|
| query = (
|
| f"Find hospitals or medical centers in {location}, Pakistan that specialize in "
|
| f"hematology and can treat patients with {disease} genetic marker in blood disorders. "
|
| f"List the top 3 with their name, contact details, address, expertise, and available treatments. "
|
| f"Format the response with markdown headings and bullet points."
|
| )
|
| else:
|
|
|
| query = (
|
| f"Find hospitals or medical centers in {location}, Pakistan that specialize in "
|
| f"treating {disease}. List the top 3 with their name, contact details, address, "
|
| f"expertise, and available treatments. Format the response with markdown headings and bullet points."
|
| )
|
|
|
| logger.info(f"Searching for hospitals treating {disease} in {location}")
|
|
|
|
|
| response = agent(query)
|
|
|
|
|
| formatted_response = _format_hospital_response(response, disease, location)
|
|
|
|
|
| HOSPITAL_CACHE[cache_key] = (time.time(), formatted_response)
|
|
|
| return formatted_response
|
|
|
| except Exception as e:
|
| error_msg = f"Error searching for hospitals: {str(e)}"
|
| logger.error(error_msg)
|
| return f"⚠️ {error_msg}\n\nPlease try again later or contact support."
|
|
|
| def _format_hospital_response(response: str, disease: str, location: str) -> str:
|
| """Format the hospital search response for better readability."""
|
|
|
| if not response or len(response.strip()) < 10:
|
| return f"No specialized hospitals found for {disease} in {location}. Please consult with a general hematologist or oncologist for referrals."
|
|
|
|
|
| formatted_response = f"""
|
| ## Hospitals Specializing in {disease} Treatment in {location}
|
|
|
| {response}
|
|
|
| ---
|
|
|
| **Disclaimer:** This information is provided for reference only. Please verify details directly with the hospitals
|
| before making any decisions. Always consult with a qualified healthcare provider for medical advice.
|
| """
|
|
|
| return formatted_response
|
|
|
| def get_disease_description(disease: str) -> str:
|
| """Get a general description of the blood disease or genetic marker."""
|
| descriptions = {
|
| "NPM1": "NPM1 is a genetic mutation commonly found in acute myeloid leukemia (AML). "
|
| "It affects the nucleophosmin protein and is generally associated with a more "
|
| "favorable prognosis compared to some other genetic markers in AML.",
|
|
|
| "PML_RARA": "PML-RARA is a fusion gene associated with acute promyelocytic leukemia (APL), "
|
| "a subtype of acute myeloid leukemia. This genetic abnormality is caused by a "
|
| "translocation between chromosomes 15 and 17, and is responsive to targeted therapies.",
|
|
|
| "RUNX1_RUNX1T1": "RUNX1-RUNX1T1 (previously known as AML1-ETO) is a fusion gene resulting "
|
| "from a translocation between chromosomes 8 and 21. It is associated with a "
|
| "specific subtype of acute myeloid leukemia (AML) that generally has a favorable prognosis.",
|
|
|
| "control": "This indicates a normal or control sample without detected genetic abnormalities "
|
| "associated with leukemia or other blood disorders."
|
| }
|
|
|
| return descriptions.get(disease, f"Information about {disease} is not available in the database.")
|
|
|
|
|
|
|
|
|
| try:
|
| api_key = st.secrets["GOOGLE_API_KEY"]
|
| genai.configure(api_key=api_key)
|
| GEMINI_MODEL_NAME = "gemini-1.5-flash"
|
| EMBEDDING_MODEL_NAME = "models/text-embedding-004"
|
|
|
| except KeyError:
|
| st.error("❌ Google API Key not found. Please ensure it's in `.streamlit/secrets.toml` as GOOGLE_API_KEY='YourKey'.")
|
| st.stop()
|
| except Exception as e:
|
| st.error(f"❌ Error configuring Google AI SDK: {e}")
|
| st.stop()
|
|
|
|
|
|
|
| @st.cache_resource
|
| def load_tf_model(model_path):
|
| """Loads a TensorFlow/Keras model, handling potential errors."""
|
| if not os.path.exists(model_path):
|
| st.error(f"Model file not found at path: {model_path}")
|
| return None
|
| try:
|
| return tf.keras.models.load_model(model_path)
|
| except Exception as e:
|
| st.error(f"Error loading TensorFlow model from {model_path}: {e}")
|
| return None
|
|
|
|
|
| BLOOD_DISEASE_MODEL_PATH = 'blood_cells_model.h5'
|
| CELL_TYPE_MODEL_PATH = 'image_classification_model.h5'
|
|
|
|
|
| blood_disease_model = load_tf_model(BLOOD_DISEASE_MODEL_PATH)
|
| cell_type_model = load_tf_model(CELL_TYPE_MODEL_PATH)
|
|
|
|
|
|
|
| disease_indicator_class_names = ["RUNX1_RUNX1T1", "control", "NPM1", "PML_RARA", "RUNX1_RUNX1T1"]
|
|
|
| cell_type_class_names = ["ig", "lymphocyte", "monocyte", "neutrophil", "platelet"]
|
|
|
|
|
|
|
|
|
| DATA_FILE_PATH = 'data.txt'
|
| FAISS_INDEX_PATH = 'faiss_index.bin'
|
| FAISS_METADATA_PATH = 'faiss_metadata.pkl'
|
| EMBEDDING_DIMENSION = 768
|
|
|
|
|
| embedding_model = None
|
|
|
|
|
| def generate_gemini_embedding(text, dimension=None):
|
| """Generate an embedding for a text using Google's Gemini embedding model."""
|
| try:
|
|
|
| embed_config = {}
|
| if dimension is not None and dimension > 0:
|
| embed_config["output_dimensionality"] = dimension
|
|
|
|
|
| result = genai.embed_content(
|
| model=EMBEDDING_MODEL_NAME,
|
| content=text,
|
| task_type="RETRIEVAL_QUERY",
|
| **embed_config
|
| )
|
|
|
|
|
| return np.array(result["embedding"], dtype=np.float32)
|
| except Exception as e:
|
| st.error(f"Error generating embedding: {e}")
|
| return None
|
|
|
|
|
| def load_faiss_index():
|
| """Load the FAISS index and metadata if they exist."""
|
| if os.path.exists(FAISS_INDEX_PATH) and os.path.exists(FAISS_METADATA_PATH):
|
| try:
|
|
|
| index = faiss.read_index(FAISS_INDEX_PATH)
|
|
|
|
|
| with open(FAISS_METADATA_PATH, 'rb') as f:
|
| metadata = pickle.load(f)
|
|
|
| return index, metadata
|
| except Exception as e:
|
| st.warning(f"Failed to load existing FAISS index: {e}. Will create a new one.")
|
| return None, None
|
| return None, None
|
|
|
|
|
| faiss_index, metadata = load_faiss_index()
|
|
|
|
|
| if faiss_index is None:
|
|
|
| faiss_index = faiss.IndexFlatL2(EMBEDDING_DIMENSION)
|
| metadata = {
|
| 'texts': [],
|
| 'urls': [],
|
| 'timestamps': []
|
| }
|
|
|
|
|
|
|
| def load_knowledge_base():
|
| """Load the knowledge base from the data.txt file."""
|
| try:
|
| with open(DATA_FILE_PATH, 'r', encoding='utf-8') as file:
|
| return file.read()
|
| except FileNotFoundError:
|
| return "No additional context data found."
|
|
|
| data_details = load_knowledge_base()
|
|
|
|
|
| SYSTEM_PROMPT = f"""You are an AI assistant specialized in providing information about blood cell types and blood diseases. Your purpose is to offer general knowledge and explanations based on established medical information.
|
|
|
| You can discuss:
|
| * Different types of blood cells (e.g., lymphocytes, monocytes, neutrophils, platelets, red blood cells) and their functions.
|
| * General information about common blood disorders, conditions, or indicators (e.g., anemia, leukemia, sickle cell disease, or specific genetic markers like NPM1 or PML-RARA mentioned in context).
|
| * Basic concepts related to blood types (e.g., ABO system, Rh factor - but not determine a user's type).
|
| * Definitions of related medical terms.
|
|
|
| Use the following specific details if relevant to the user's question and within your scope:
|
| ---
|
| {data_details}
|
| ---
|
|
|
| **IMPORTANT LIMITATIONS: You MUST strictly adhere to the following:**
|
| * **DO NOT provide medical diagnoses.** You cannot tell a user if they have a specific disease.
|
| * **DO NOT interpret personal medical data,** such as lab results or medical images.
|
| * **DO NOT offer medical advice,** treatment recommendations, or suggestions on managing health conditions.
|
| * **DO NOT act as a substitute for a qualified healthcare professional.** Your information is for general knowledge only.
|
|
|
| **If a user asks for a diagnosis, medical advice, interpretation of their personal results/images, or asks 'what disease do I have?', you MUST politely refuse.** State clearly that you are an informational AI assistant and cannot provide medical services. **Strongly advise the user to consult with a doctor or qualified healthcare provider** for any personal health concerns, diagnosis, or treatment.
|
|
|
| Keep your responses informative, factual, objective, and strictly within the boundaries of providing general educational information. Avoid speculation.
|
| """
|
|
|
|
|
| st.title("🩸 AI Chatbot & Blood Classifier")
|
|
|
|
|
| st.header("💬 AI Chat Assistant")
|
| st.caption("Ask questions about blood cells and related diseases (general information only).")
|
|
|
|
|
| if "messages" not in st.session_state:
|
| st.session_state.messages = []
|
|
|
|
|
| chat_container = st.container()
|
| with chat_container:
|
| for message in st.session_state.messages:
|
| with st.chat_message(message["role"]):
|
| st.markdown(message["content"])
|
|
|
|
|
| if prompt := st.chat_input("Your question..."):
|
| st.session_state.messages.append({"role": "user", "content": prompt})
|
| with chat_container:
|
| with st.chat_message("user"):
|
| st.markdown(prompt)
|
|
|
|
|
| if faiss_index.ntotal > 0:
|
| try:
|
|
|
| query_embedding = generate_gemini_embedding(prompt, dimension=EMBEDDING_DIMENSION)
|
|
|
| if query_embedding is not None:
|
|
|
| query_embedding = query_embedding.reshape(1, -1)
|
|
|
|
|
| k = min(3, faiss_index.ntotal)
|
| distances, indices = faiss_index.search(query_embedding, k)
|
|
|
|
|
| relevant_texts = [metadata['texts'][idx] for idx in indices[0]]
|
|
|
|
|
| if len(relevant_texts) > 0 and distances[0][0] < 20:
|
| context_from_faiss = "\n\nRelevant information from knowledge base:\n" + "\n---\n".join(relevant_texts)
|
| full_prompt = SYSTEM_PROMPT + context_from_faiss + "\n\nUser: " + prompt + "\n\nAssistant:"
|
| else:
|
| full_prompt = SYSTEM_PROMPT + "\n\nUser: " + prompt + "\n\nAssistant:"
|
| else:
|
| full_prompt = SYSTEM_PROMPT + "\n\nUser: " + prompt + "\n\nAssistant:"
|
|
|
| except Exception as e:
|
| st.warning(f"Error searching knowledge base: {e}")
|
| full_prompt = SYSTEM_PROMPT + "\n\nUser: " + prompt + "\n\nAssistant:"
|
| else:
|
| full_prompt = SYSTEM_PROMPT + "\n\nUser: " + prompt + "\n\nAssistant:"
|
|
|
| try:
|
| with st.spinner("Thinking..."):
|
| model = genai.GenerativeModel(GEMINI_MODEL_NAME)
|
| response = model.generate_content(contents=[full_prompt])
|
|
|
| if response.parts:
|
| assistant_response = response.text
|
| elif not response.candidates:
|
| assistant_response = "Response may have been blocked due to safety settings or contains no content."
|
| try:
|
| with chat_container: st.warning(f"Feedback: {response.prompt_feedback}")
|
| except Exception: pass
|
| else:
|
| assistant_response = "Received an unexpected response structure."
|
| with chat_container: st.warning(f"Unexpected response: {response}")
|
| except Exception as e:
|
| st.error(f"❌ Error generating content: {e}")
|
| assistant_response = f"Sorry, an error occurred: {e}"
|
|
|
| st.session_state.messages.append({"role": "assistant", "content": assistant_response})
|
| st.rerun()
|
|
|
|
|
| st.divider()
|
| st.header("🔬 Blood Image Classifier")
|
|
|
| if blood_disease_model is None or cell_type_model is None:
|
| st.error("One or more classification models failed to load. Cannot proceed.")
|
| else:
|
| col1, col2 = st.columns([1, 2])
|
| with col1:
|
| option = st.selectbox(
|
| "Choose classification mode:",
|
| ("Blood Disease", "Blood Cell Type Classification"),
|
| key="classification_type",
|
| help="Blood Disease: Predicts disease indicators (e.g., NPM1). Cell Type: Predicts cell types (e.g., lymphocyte)."
|
| )
|
|
|
|
|
| if option == "Blood Disease":
|
| location = st.selectbox(
|
| "Select location for hospital search:",
|
| ["Lahore", "Karachi", "Islamabad", "Multan", "Faisalabad", "Peshawar"],
|
| key="location_selector",
|
| help="Select your location to find nearby hospitals that specialize in the detected disease."
|
| )
|
|
|
| uploaded_file = st.file_uploader(
|
| "Upload a blood cell image",
|
| type=['png', 'jpg', 'jpeg', 'tiff'],
|
| key="file_uploader"
|
| )
|
|
|
| def preprocess_and_predict(image, model, class_names, target_size=(64, 64)):
|
| """Preprocess image and predict class."""
|
| try:
|
| img_resized = image.resize(target_size)
|
| if img_resized.mode != 'RGB': img_resized = img_resized.convert('RGB')
|
| img_array = np.array(img_resized) / 255.0
|
| img_array = np.expand_dims(img_array, axis=0)
|
| predictions = model.predict(img_array)
|
| pred_index = np.argmax(predictions, axis=1)[0]
|
| if pred_index < len(class_names):
|
| predicted_class = class_names[pred_index]
|
| else:
|
| st.error(f"Pred index {pred_index} out of bounds (len {len(class_names)}).")
|
| return None, None
|
| confidence = np.max(predictions) * 100
|
| return predicted_class, confidence
|
| except Exception as e:
|
| st.error(f"Image processing/prediction error: {e}")
|
| return None, None
|
|
|
| with col2:
|
| if uploaded_file is not None:
|
| try:
|
| image = Image.open(uploaded_file)
|
| st.image(image, caption="Uploaded Image", use_column_width=True)
|
| predicted_class = None
|
| confidence = None
|
| with st.spinner("Analyzing image..."):
|
| if option == "Blood Disease":
|
| predicted_class, confidence = preprocess_and_predict(
|
| image, blood_disease_model, disease_indicator_class_names, target_size=(224, 224)
|
| )
|
| elif option == "Blood Cell Type Classification":
|
| predicted_class, confidence = preprocess_and_predict(
|
| image, cell_type_model, cell_type_class_names, target_size=(64, 64)
|
| )
|
| if predicted_class is not None and confidence is not None:
|
| if option == "Blood Disease":
|
| st.success(f"Predicted Disease Indicator: **{predicted_class}**")
|
|
|
|
|
| disease_description = get_disease_description(predicted_class)
|
| st.info(f"**About this marker:** {disease_description}")
|
|
|
|
|
| location = st.session_state.get("location_selector", "Lahore")
|
| with st.expander(f"Find hospitals for {predicted_class} in {location}", expanded=True):
|
| if st.button("Search for Specialized Hospitals"):
|
| with st.spinner(f"Searching for hospitals that specialize in {predicted_class} in {location}..."):
|
| if hospital_search_available and agent:
|
| try:
|
| response = search_hospitals(agent, predicted_class, location)
|
| st.markdown("### Hospital Recommendations")
|
| st.markdown(response)
|
| except Exception as e:
|
| st.error(f"Error searching for hospitals: {e}")
|
| else:
|
| st.warning("Hospital search is not available because AgentPro could not be initialized.")
|
| else:
|
| st.success(f"Predicted Cell Type: **{predicted_class}**")
|
|
|
| st.metric(label="Confidence", value=f"{confidence:.2f}%")
|
| else:
|
| st.warning("Could not make a prediction.")
|
| except Exception as e:
|
| st.error(f"Error handling uploaded image: {e}")
|
| elif option:
|
| placeholder = st.empty()
|
| placeholder.info("Upload an image using the panel on the left.")
|
|
|
|
|
| st.divider()
|
| st.header("🌐 Web Content Extractor with FAISS Embedding")
|
| st.caption("Enter a URL to extract its main text content and save it to your knowledge base.")
|
|
|
|
|
| def scrape_website_content(url):
|
| """Attempts to scrape paragraphs and headings from a given URL."""
|
| try:
|
|
|
| if not url.startswith(('http://', 'https://')):
|
| url = 'https://' + url
|
|
|
| headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
|
| response = requests.get(url, headers=headers, timeout=10)
|
| response.raise_for_status()
|
| soup = BeautifulSoup(response.text, 'html.parser')
|
|
|
| paragraphs = soup.find_all('p')
|
| headers = soup.find_all(['h1', 'h2', 'h3'])
|
|
|
| content_parts = [h.get_text(strip=True) for h in headers if h.get_text(strip=True)]
|
| content_parts.extend([p.get_text(strip=True) for p in paragraphs if p.get_text(strip=True)])
|
| content = '\n\n'.join(content_parts)
|
| if not content: return "Could not find significant text content using p/h1/h2/h3 tags."
|
| return content
|
| except requests.exceptions.RequestException as e: return f"Error fetching URL {url}: {str(e)}"
|
| except Exception as e: return f"Error processing {url}: {str(e)}"
|
|
|
|
|
| def save_to_knowledge_base(url, content):
|
| """Save the content to data.txt and update FAISS embeddings using Gemini."""
|
|
|
| if content.startswith("Error") or content.startswith("Could not find"):
|
| return False, content
|
|
|
|
|
| try:
|
| with open(DATA_FILE_PATH, 'a', encoding='utf-8') as file:
|
| file.write(f"\n\n--- CONTENT FROM: {url} ---\n")
|
| file.write(content)
|
| file.write("\n--- END CONTENT ---\n")
|
| except Exception as e:
|
| return False, f"Error saving to data.txt: {e}"
|
|
|
|
|
| try:
|
|
|
| chunks = []
|
| current_chunk = ""
|
| for paragraph in content.split('\n\n'):
|
| if len(current_chunk) + len(paragraph) < 1000:
|
| if current_chunk: current_chunk += "\n\n"
|
| current_chunk += paragraph
|
| else:
|
| if current_chunk:
|
| chunks.append(current_chunk)
|
| current_chunk = paragraph
|
| if current_chunk:
|
| chunks.append(current_chunk)
|
|
|
|
|
| embedding_success_count = 0
|
| for chunk in chunks:
|
|
|
| embedding = generate_gemini_embedding(chunk, dimension=EMBEDDING_DIMENSION)
|
|
|
| if embedding is not None:
|
|
|
| embedding = embedding.reshape(1, -1)
|
| faiss_index.add(embedding)
|
|
|
|
|
| metadata['texts'].append(chunk)
|
| metadata['urls'].append(url)
|
| metadata['timestamps'].append(time.time())
|
|
|
| embedding_success_count += 1
|
|
|
|
|
| faiss.write_index(faiss_index, FAISS_INDEX_PATH)
|
| with open(FAISS_METADATA_PATH, 'wb') as f:
|
| pickle.dump(metadata, f)
|
|
|
| return True, f"Successfully added {embedding_success_count} chunks to knowledge base with Gemini embeddings."
|
| except Exception as e:
|
| return False, f"Error creating embeddings: {e}"
|
|
|
|
|
| user_url = st.text_input("Enter URL:", key="url_input", placeholder="e.g., https://www.example-health-info.com")
|
| col1, col2 = st.columns([1, 1])
|
|
|
| with col1:
|
| if st.button("Extract & Save to Knowledge Base", key="scrape_save_button"):
|
| if user_url:
|
| with st.spinner(f"Extracting content from {user_url}..."):
|
|
|
| scraped_content = scrape_website_content(user_url)
|
| st.text_area("Extracted Content:", scraped_content, height=200)
|
|
|
|
|
| if not scraped_content.startswith("Error") and not scraped_content.startswith("Could not find"):
|
| with st.spinner("Saving to knowledge base and creating embeddings with Gemini..."):
|
| success, message = save_to_knowledge_base(user_url, scraped_content)
|
| if success:
|
| st.success(message)
|
| else:
|
| st.error(message)
|
| else:
|
| st.warning("Please enter a URL first.")
|
|
|
| with col2:
|
| if st.button("View Current Knowledge Base Stats", key="view_kb_stats"):
|
| with st.spinner("Analyzing knowledge base..."):
|
|
|
| if os.path.exists(DATA_FILE_PATH):
|
| with open(DATA_FILE_PATH, 'r', encoding='utf-8') as file:
|
| content = file.read()
|
| total_chars = len(content)
|
| total_lines = content.count('\n') + 1
|
| urls_count = content.count('--- CONTENT FROM:')
|
|
|
| st.info(f"""
|
| 📚 **Knowledge Base Stats**
|
| - Total characters: {total_chars:,}
|
| - Total lines: {total_lines:,}
|
| - Sources: {urls_count} URLs
|
| """)
|
| else:
|
| st.info("No knowledge base file found.")
|
|
|
|
|
| if faiss_index.ntotal > 0:
|
| st.info(f"""
|
| 🔍 **FAISS Index Stats**
|
| - Total embeddings: {faiss_index.ntotal:,}
|
| - Unique sources: {len(set(metadata['urls'])):,}
|
| - Embedding dimension: {faiss_index.d}
|
| - Embedding model: {EMBEDDING_MODEL_NAME}
|
| """)
|
| else:
|
| st.info("FAISS index is empty.")
|
|
|
| st.caption("Note: Extraction success depends on website structure and permissions. Respect website terms.")
|
|
|
|
|
| st.divider()
|
| st.header("⚙️ TensorFlow Model Details")
|
|
|
| selected_option_for_summary = st.session_state.get("classification_type", "Blood Disease")
|
| with st.expander(f"Show TF Model Summary for '{selected_option_for_summary}'"):
|
| model_to_show = None
|
| if selected_option_for_summary == "Blood Disease": model_to_show = blood_disease_model
|
| elif selected_option_for_summary == "Blood Cell Type Classification": model_to_show = cell_type_model
|
| if model_to_show:
|
| summary_lines = []
|
| model_to_show.summary(print_fn=lambda x: summary_lines.append(x))
|
| st.text('\n'.join(summary_lines))
|
| else: st.warning("Selected classification model could not be loaded/found.")
|
|
|