Freesia090425/FDA-Tracking-091925
0
1import streamlit as st2import pandas as pd3import numpy as np4import plotly.express as px5import google.generativeai as genai6from google.api_core import exceptions7from google.generativeai.types import HarmCategory, HarmBlockThreshold8from io import StringIO9from sklearn.cluster import KMeans10from sklearn.preprocessing import StandardScaler11from sklearn.decomposition import PCA12from sklearn.ensemble import IsolationForest13import warnings14import yaml15import random16import time17 18warnings.filterwarnings('ignore')19 20# --- Configuration and Initialization ---21 22# (Bug Fix & Performance) Use st.cache_resource for objects that should be created only once.23@st.cache_resource24def configure_gemini():25 """Configure the Gemini API, stopping gracefully if the key is not found."""26 try:27 api_key = st.secrets.get("GEMINI_API_KEY")28 if not api_key:29 st.error("GEMINI_API_KEY not found. Please add it to your Hugging Face Space secrets.")30 st.stop()31 genai.configure(api_key=api_key)32 return genai.GenerativeModel('gemini-2.0-flash')33 except Exception as e:34 st.error(f"Failed to configure Gemini API: {e}")35 st.stop()36 37# (Performance) Use st.cache_data to load agents from YAML only once.38@st.cache_data39def load_agent_config():40 """Load the specialized AI agent configuration from agents.yaml."""41 try:42 with open('agents.yaml', 'r') as f:43 config = yaml.safe_load(f)44 # Create a mapping from agent name to its full dictionary for easy access45 return {agent['name']: agent for agent in config.get('agents', [])}46 except FileNotFoundError:47 st.error("FATAL: agents.yaml not found. This file is required for the new architecture.")48 st.info("Please create an agents.yaml file based on the provided spec.")49 # Return a minimal default agent to prevent a hard crash50 return {51 "ChiefEngineerAgent": {52 "name": "ChiefEngineerAgent",53 "description": "Default agent. Please create agents.yaml.",54 "specialty": "General tasks",55 "status": "DEGRADED",56 "prompt_template": "You are a helpful AI assistant. Analyze the user's query: {query} based on the data with columns: {columns}. Here is a preview: {preview}"57 }58 }59 except Exception as e:60 st.error(f"Error parsing agents.yaml: {e}")61 st.stop()62 63 64def initialize_session_state():65 """Initialize session state variables for the new UI and logic."""66 defaults = {67 'chat_history': [],68 'datasets': {},69 'current_dataset_name': None,70 'selected_agent': "ChiefEngineerAgent", # Default to the orchestrator71 'active_view': 'data_management', # Controls what's shown in the main panel72 'analysis_result': None, # To store results from data mining73 'viz_fig': None # To store generated figures74 }75 for key, value in defaults.items():76 if key not in st.session_state:77 st.session_state[key] = value78 79# --- Ferrari-Style UI Components ---80 81def apply_ferrari_css():82 """Apply the enhanced Ferrari Racing Edition CSS from the spec."""83 spec_css = """84 <style>85 @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&display=swap');86 * { box-sizing: border-box; }87 body { font-family: 'Orbitron', monospace; }88 .stApp {89 background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%);90 color: #ffffff;91 }92 .racing-stripes {93 position: fixed; top: 0; left: 0; right: 0; height: 4px;94 background: linear-gradient(90deg, #ff0000, #ffffff, #ff0000);95 z-index: 1000; animation: pulse 2s infinite;96 }97 @keyframes pulse {98 0%, 100% { opacity: 1; } 50% { opacity: 0.7; }99 }100 .header {101 background: linear-gradient(135deg, #2c0e0e, #4a0e0e, #2c0e0e);102 padding: 10px; text-align: center;103 box-shadow: 0 4px 20px rgba(255, 0, 0, 0.3);104 margin-bottom: 1rem;105 }106 .ferrari-logo {107 font-size: 2.5rem; font-weight: 900; color: #ff0000;108 text-shadow: 0 0 20px rgba(255, 0, 0, 0.8);109 }110 .tagline { font-size: 1rem; color: #cccccc; letter-spacing: 2px; }111 .agent-panel, .main-display {112 background: linear-gradient(135deg, #1e1e1e, #2a2a2a);113 border-radius: 15px; border: 2px solid #ff0000;114 box-shadow: 0 8px 32px rgba(255, 0, 0, 0.2);115 padding: 15px; height: 75vh; overflow-y: auto;116 }117 .main-display {118 border-color: #00ffff;119 box-shadow: 0 8px 32px rgba(0, 255, 255, 0.2);120 }121 .agent-header {122 background: linear-gradient(135deg, #ff0000, #cc0000);123 color: white; padding: 10px; font-weight: bold;124 text-align: center; border-radius: 13px 13px 0 0; margin: -15px -15px 10px -15px;125 }126 .agent-card {127 margin-bottom: 10px; padding: 10px;128 background: linear-gradient(135deg, #2a2a2a, #3a3a3a);129 border-radius: 10px; border-left: 4px solid #ff0000;130 cursor: pointer; transition: all 0.3s ease;131 }132 .agent-card-selected {133 border-left: 4px solid #00ffff;134 transform: scale(1.02);135 box-shadow: 0 4px 16px rgba(0, 255, 255, 0.4);136 }137 .agent-name { font-weight: bold; color: #ff6666; margin-bottom: 5px; }138 .agent-specialty { font-size: 0.8rem; color: #cccccc; }139 .agent-status { font-size: 0.8rem; color: #00ff00; }140 .ferrari-button {141 background: linear-gradient(135deg, #ff0000, #cc0000); color: white;142 border: none; border-radius: 25px; padding: 15px 30px;143 font-size: 1.1rem; font-weight: bold; cursor: pointer;144 transition: all 0.3s ease; width: 100%;145 box-shadow: 0 4px 16px rgba(255, 0, 0, 0.3);146 }147 .ferrari-button:hover {148 transform: translateY(-2px);149 box-shadow: 0 6px 20px rgba(255, 0, 0, 0.5);150 }151 .stTextInput > div > div > input {152 background: rgba(0, 0, 0, 0.8); color: white; border: 2px solid #00ffff;153 border-radius: 25px; padding: 15px 25px; font-size: 1.1rem;154 font-family: 'Orbitron', monospace;155 }156 .user-message {157 background: linear-gradient(135deg, #333333, #444444); color: white;158 border-radius: 20px 20px 5px 20px; padding: 15px; margin: 15px 0; text-align: right;159 }160 .ai-message {161 background: linear-gradient(135deg, var(--ferrari-red, #ff0000), var(--ferrari-dark-red, #cc0000)); color: white;162 border-radius: 20px 20px 20px 5px; padding: 15px; margin: 15px 0; text-align: left;163 }164 </style>165 """166 st.markdown(spec_css, unsafe_allow_html=True)167 st.markdown('<div class="racing-stripes"></div>', unsafe_allow_html=True)168 st.markdown("""169 <header class="header">170 <h1 class="ferrari-logo">๐๏ธ ReguSight AI</h1>171 <p class="tagline">FERRARI RACING EDITION - PRECISION โข SPEED โข INTELLIGENCE</p>172 </header>173 """, unsafe_allow_html=True)174 175 176# --- AI & Data Functions ---177 178@st.cache_data179def perform_data_mining(df, analysis_type, n_clusters=3):180 """Consolidated function for all data mining tasks."""181 df_copy = df.copy()182 numeric_cols = df_copy.select_dtypes(include=np.number).columns183 if len(numeric_cols) < 2 and analysis_type != "Anomaly Detection":184 return None, "Not enough numeric columns for this analysis."185 if not numeric_cols.any():186 return None, "No numeric columns found for analysis."187 188 try:189 if analysis_type == "Clustering":190 scaler = StandardScaler()191 scaled_data = scaler.fit_transform(df_copy[numeric_cols].fillna(0))192 kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init='auto')193 df_copy['Cluster'] = kmeans.fit_predict(scaled_data)194 return df_copy, f"Successfully identified {n_clusters} clusters."195 elif analysis_type == "Anomaly Detection":196 iso_forest = IsolationForest(contamination='auto', random_state=42)197 df_copy['Is_Anomaly'] = iso_forest.fit_predict(df_copy[numeric_cols].fillna(0)) == -1198 count = df_copy['Is_Anomaly'].sum()199 return df_copy, f"Detected {count} potential anomalies."200 elif analysis_type == "PCA":201 scaler = StandardScaler()202 scaled_data = scaler.fit_transform(df_copy[numeric_cols].fillna(0))203 pca = PCA(n_components=min(3, len(numeric_cols)))204 pca_result = pca.fit_transform(scaled_data)205 for i in range(pca.n_components_):206 df_copy[f'PC{i+1}'] = pca_result[:, i]207 var_explained = ', '.join([f'{v:.2%}' for v in pca.explained_variance_ratio_])208 return df_copy, f"PCA completed. Variance explained: {var_explained}"209 except Exception as e:210 return None, f"Analysis failed: {e}"211 return None, "Invalid analysis type specified."212 213 214def generate_gemini_content(prompt: str) -> str:215 """Generic function to call Gemini API with robust error handling."""216 try:217 model = configure_gemini()218 response = model.generate_content(219 prompt,220 safety_settings={HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE},221 request_options={"timeout": 120} # Increased timeout for complex analysis222 )223 return response.text224 except exceptions.GoogleAPICallError as e:225 return f"**Error:** Gemini API call failed: {e.message}"226 except Exception as e:227 return f"**Error:** An unexpected error occurred: {e}"228 229 230# --- UI Panel Display Functions ---231 232def display_left_panel(agents):233 """Displays the agent selection panel."""234 st.markdown('<div class="agent-header">๐ฏ ACTIVE PIT CREW</div>', unsafe_allow_html=True)235 for agent_name, agent_data in agents.items():236 is_selected = (st.session_state.selected_agent == agent_name)237 card_class = "agent-card-selected" if is_selected else ""238 # Using st.expander as a clickable container239 with st.container():240 st.markdown(f"""241 <div class="agent-card {card_class}">242 <div class="agent-name">{agent_data.get('name', 'N/A')}</div>243 <div class="agent-specialty">{agent_data.get('specialty', '')}</div>244 <div class="agent-status">โ {agent_data.get('status', 'READY').upper()}</div>245 </div>246 """, unsafe_allow_html=True)247 # This button is invisible but makes the div clickable248 if st.button(f"select_{agent_name}", key=f"btn_{agent_name}", use_container_width=True):249 st.session_state.selected_agent = agent_name250 # Automatically switch view to chat when a new agent is selected251 st.session_state.active_view = 'chat'252 st.rerun()253 254def display_right_panel():255 """Displays the system metrics and controls panel."""256 st.markdown('<div class="agent-header">โก SYSTEM & DATA</div>', unsafe_allow_html=True)257 if st.button("๐๏ธ Data Management", use_container_width=True):258 st.session_state.active_view = 'data_management'259 st.rerun()260 if st.button("๐ฌ Agent Chat", use_container_width=True, disabled=not st.session_state.current_dataset_name):261 st.session_state.active_view = 'chat'262 st.rerun()263 if st.button("๐ฌ Data Mining", use_container_width=True, disabled=not st.session_state.current_dataset_name):264 st.session_state.active_view = 'data_mining'265 st.rerun()266 267 st.markdown("---")268 if st.session_state.current_dataset_name:269 st.info(f"Active Dataset: **{st.session_state.current_dataset_name}**")270 df = st.session_state.datasets[st.session_state.current_dataset_name]271 st.write(f"Shape: `{df.shape}`")272 st.metric("Memory Usage", f"{df.memory_usage(deep=True).sum() / 1e6:.2f} MB")273 else:274 st.warning("No active dataset. Go to Data Management to load data.")275 276def display_main_panel(agents):277 """Displays the main interactive content based on the active view."""278 active_view = st.session_state.active_view279 280 if active_view == 'data_management':281 display_data_management_view()282 elif st.session_state.current_dataset_name is None:283 st.warning("Please load a dataset from the Data Management view to proceed.")284 if st.button("Go to Data Management"):285 st.session_state.active_view = 'data_management'286 st.rerun()287 elif active_view == 'chat':288 display_chat_view(agents)289 elif active_view == 'data_mining':290 display_data_mining_view()291 292 293def display_data_management_view():294 st.header("๐๏ธ Dataset Management")295 col1, col2 = st.columns(2)296 with col1:297 with st.container(border=True):298 name = st.text_input("New Dataset Name (required):")299 uploaded_file = st.file_uploader("Upload CSV/JSON Dataset", type=['csv', 'json'])300 if uploaded_file and name:301 try:302 if uploaded_file.name.endswith('.csv'):303 st.session_state.datasets[name] = pd.read_csv(uploaded_file)304 else:305 st.session_state.datasets[name] = pd.read_json(uploaded_file)306 st.success(f"Dataset '{name}' loaded!")307 st.session_state.current_dataset_name = name308 st.session_state.active_view = 'chat' # Switch to chat after upload309 st.rerun()310 except Exception as e:311 st.error(f"Error loading file: {e}")312 with col2:313 with st.container(border=True):314 st.selectbox(315 "Select Active Dataset:",316 options=[None] + list(st.session_state.datasets.keys()),317 key='current_dataset_name'318 )319 st.info("Changing the active dataset will reset the chat.")320 if st.session_state.current_dataset_name and st.session_state.chat_history:321 st.session_state.chat_history = []322 323 324 if st.session_state.current_dataset_name:325 st.header("Data Preview")326 df = st.session_state.datasets[st.session_state.current_dataset_name]327 st.dataframe(df.head())328 329 330def display_chat_view(agents):331 """The main chat and analysis interface."""332 st.header(f"๐ฌ Chat with: {st.session_state.selected_agent}")333 334 # Display chat history335 for entry in st.session_state.chat_history:336 role = entry.get('role', 'ai')337 message = entry.get('parts', [''])[0]338 if role == 'user':339 st.markdown(f'<div class="user-message">{message}</div>', unsafe_allow_html=True)340 else:341 st.markdown(f'<div class="ai-message">{message}</div>', unsafe_allow_html=True)342 343 # Chat input form344 with st.form(key='chat_form', clear_on_submit=True):345 user_query = st.text_input("Enter your regulatory intelligence query...", key='chat_input_widget', placeholder="e.g., 'Find anomalies in adverse events'")346 submitted = st.form_submit_button("๐ ENGAGE TURBO ANALYSIS")347 348 if submitted and user_query:349 st.session_state.chat_history.append({'role': 'user', 'parts': [user_query]})350 with st.spinner("AI is thinking..."):351 df_current = st.session_state.datasets[st.session_state.current_dataset_name]352 agent = agents[st.session_state.selected_agent]353 prompt_template = agent.get("prompt_template", "Analyze this: {query}")354 355 # Construct the detailed prompt356 prompt = prompt_template.format(357 query=user_query,358 columns=df_current.columns.tolist(),359 preview=df_current.head().to_string()360 )361 362 ai_response = generate_gemini_content(prompt)363 st.session_state.chat_history.append({'role': 'model', 'parts': [ai_response]})364 st.rerun()365 366def display_data_mining_view():367 st.header("๐ฌ Advanced Data Mining")368 df_current = st.session_state.datasets[st.session_state.current_dataset_name]369 370 # Select analysis type371 analysis_type = st.selectbox("Select Analysis Type", ["Clustering", "Anomaly Detection", "PCA"])372 n_clusters = 3373 if analysis_type == "Clustering":374 n_clusters = st.slider("Number of Clusters", 2, 10, 3)375 376 if st.button(f"Run {analysis_type}"):377 with st.spinner(f"Performing {analysis_type}..."):378 result_df, msg = perform_data_mining(df_current, analysis_type, n_clusters)379 st.success(msg)380 if result_df is not None:381 st.session_state.analysis_result = result_df382 st.dataframe(result_df.head())383 384 # Display results if they exist385 if st.session_state.analysis_result is not None:386 st.subheader("Analysis Results Preview")387 st.dataframe(st.session_state.analysis_result.head())388 # Add a plot for the results389 try:390 if 'Cluster' in st.session_state.analysis_result.columns:391 st.plotly_chart(px.scatter(st.session_state.analysis_result,392 x=df_current.columns[0], y=df_current.columns[1], color='Cluster',393 title="Clustering Results"), use_container_width=True)394 elif 'Is_Anomaly' in st.session_state.analysis_result.columns:395 st.plotly_chart(px.scatter(st.session_state.analysis_result,396 x=df_current.columns[0], y=df_current.columns[1], color='Is_Anomaly',397 title="Anomaly Detection Results"), use_container_width=True)398 except Exception as e:399 st.warning(f"Could not generate plot for results: {e}")400 401# --- Main Application Logic ---402 403def main():404 st.set_page_config(layout="wide", page_title="ReguSight AI - Ferrari Edition")405 406 # Load configs and initialize state407 configure_gemini()408 agents = load_agent_config()409 initialize_session_state()410 411 # Apply the Ferrari UI412 apply_ferrari_css()413 414 # Create the main 3-column layout415 col1, col2, col3 = st.columns([0.25, 0.5, 0.25])416 417 with col1:418 with st.container(height=750): # Pinned height419 display_left_panel(agents)420 421 with col2:422 with st.container(height=750): # Pinned height423 display_main_panel(agents)424 425 with col3:426 with st.container(height=750): # Pinned height427 display_right_panel()428 429 430if __name__ == '__main__':431 main()