CoolFace
Apppublic

Freesia090425/FDA-Tracking-091925

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
app.py431 linesDownload Raw Back to root
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()