# app.py import gradio as gr import pandas as pd import os import logging import matplotlib matplotlib.use('Agg') # Set backend for Matplotlib to avoid GUI conflicts with Gradio # import matplotlib.pyplot as plt # Not directly used in app.py anymore import time # For profiling if needed # from datetime import datetime, timedelta # Not directly used in app.py # import numpy as np # Not directly used in app.py # from collections import OrderedDict, defaultdict # Not directly used in app.py import asyncio # For async operations # --- Module Imports --- from utils.gradio_utils import get_url_user_token # Configuration (assuming these exist and are correctly defined) from config import ( LINKEDIN_CLIENT_ID_ENV_VAR, BUBBLE_APP_NAME_ENV_VAR, BUBBLE_API_KEY_PRIVATE_ENV_VAR, BUBBLE_API_ENDPOINT_ENV_VAR, # PLOT_ID_TO_FORMULA_KEY_MAP # Used in analytics_handlers ) # from formulas import PLOT_FORMULAS # Used in analytics_handlers # Services (assuming these exist and are correctly defined) from services.state_manager import process_and_store_bubble_token from services.sync_logic import sync_all_linkedin_data_orchestrator # UI Generators (assuming these exist and are correctly defined) from ui.ui_generators import display_main_dashboard #, build_analytics_tab_plot_area, BOMB_ICON, EXPLORE_ICON, FORMULA_ICON, ACTIVE_ICON # Tab Setup UI functions from ui.dashboard_sync_tab import setup_dashboard_sync_tab from ui.analytics_tab_setup import setup_analytics_tab from ui.agentic_report_tab import setup_agentic_report_tab from ui.agentic_okrs_tab import setup_agentic_okrs_tab # Handler Classes from handlers.dashboard_sync_handlers import DashboardSyncHandlers from handlers.analytics_handlers import AnalyticsHandlers from handlers.agentic_handlers import AgenticHandlers, AGENTIC_MODULES_LOADED as AGENTIC_HANDLERS_MODULES_LOADED # Check consistency of AGENTIC_MODULES_LOADED # This flag is crucial for conditional UI rendering and functionality. # The one from AgenticHandlers is based on its own try-except for its specific imports. # We might need a global one if app.py itself tries to import agentic modules directly. # For now, using the one from AgenticHandlers as it's most relevant to agentic functionality. APP_AGENTIC_MODULES_LOADED = AGENTIC_HANDLERS_MODULES_LOADED # Configure logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(message)s') # API Key Setup os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "False" user_provided_api_key = os.environ.get("GEMINI_API_KEY") if user_provided_api_key: os.environ["GOOGLE_API_KEY"] = user_provided_api_key logging.info("GOOGLE_API_KEY environment variable has been set from GEMINI_API_KEY.") else: logging.error("CRITICAL ERROR: The API key environment variable 'GEMINI_API_KEY' was not found.") # --- Main Gradio Application --- with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"), title="LinkedIn Organization Dashboard") as app: # --- Global States --- token_state = gr.State(value={ "token": None, "client_id": None, "org_urn": None, "bubble_posts_df": pd.DataFrame(), "bubble_post_stats_df": pd.DataFrame(), "bubble_mentions_df": pd.DataFrame(), "bubble_follower_stats_df": pd.DataFrame(), "fetch_count_for_api": 0, "url_user_token_temp_storage": None, "config_date_col_posts": "published_at", "config_date_col_mentions": "date", "config_date_col_followers": "date", "config_media_type_col": "media_type", "config_eb_labels_col": "li_eb_label" }) # States for analytics tab chatbot chat_histories_st = gr.State({}) # Stores chat histories for each plot_id {plot_id: [{"role":"user",...}]} current_chat_plot_id_st = gr.State(None) # ID of the plot currently active in chat plot_data_for_chatbot_st = gr.State({}) # Stores summaries for plots {plot_id: "summary text"} # States for analytics tab panel management active_panel_action_state = gr.State(None) # Tracks current active panel e.g. {"plot_id": "X", "type": "insights"} explored_plot_id_state = gr.State(None) # Tracks if a plot is being "explored" (others hidden) # States for Agentic Pipeline orchestration_raw_results_st = gr.State(None) # Stores raw output from run_full_analytics_orchestration key_results_for_selection_st = gr.State([]) # Stores list of dicts for KR checkbox group choices selected_key_result_ids_st = gr.State([]) # Stores list of selected KR unique IDs from checkbox group (though CBG value is source of truth) # --- Hidden Components for URL Params --- gr.Markdown("# 🚀 LinkedIn Organization Dashboard") url_user_token_display = gr.Textbox(label="User Token (Nascosto)", interactive=False, visible=False) status_box = gr.Textbox(label="Stato Generale Token LinkedIn", interactive=False, value="Inizializzazione...") org_urn_display = gr.Textbox(label="URN Organizzazione (Nascosto)", interactive=False, visible=False) # --- Load URL parameters --- # This runs on app load to fetch params from the URL query string app.load(fn=get_url_user_token, inputs=None, outputs=[url_user_token_display, org_urn_display], api_name="get_url_params", show_progress=False) # --- UI Setup for Tabs --- with gr.Tabs() as tabs: with gr.TabItem("1️⃣ Dashboard & Sync", id="tab_dashboard_sync"): dashboard_sync_components = setup_dashboard_sync_tab() with gr.TabItem("2️⃣ Analisi Grafici", id="tab_analytics"): analytics_components = setup_analytics_tab() # This returns a dict of all components in the tab # Agentic tabs are conditional agentic_report_components = {} agentic_okrs_components = {} with gr.TabItem("3️⃣ Agentic Analysis Report", id="tab_agentic_report", visible=APP_AGENTIC_MODULES_LOADED): agentic_report_components = setup_agentic_report_tab(APP_AGENTIC_MODULES_LOADED) with gr.TabItem("4️⃣ Agentic OKRs & Tasks", id="tab_agentic_okrs", visible=APP_AGENTIC_MODULES_LOADED): agentic_okrs_components = setup_agentic_okrs_tab(APP_AGENTIC_MODULES_LOADED) # --- Initialize Handlers --- dashboard_sync_handler = DashboardSyncHandlers( dashboard_components, token_state, url_user_token_display, org_urn_display, status_box ) analytics_handler = AnalyticsHandlers( analytics_components, token_state, chat_histories_st, current_chat_plot_id_st, plot_data_for_chatbot_st, active_panel_action_state, explored_plot_id_state ) agentic_handler = None if APP_AGENTIC_MODULES_LOADED: agentic_handler = AgenticHandlers( agentic_report_components, agentic_okrs_components, token_state, orchestration_raw_results_st, key_results_for_selection_st, selected_key_result_ids_st ) # --- Setup Event Handlers from Handler Classes --- # Dashboard/Sync handlers are mostly involved in chained events below analytics_handler.setup_event_handlers() # Sets up internal events for analytics tab if APP_AGENTIC_MODULES_LOADED and agentic_handler: agentic_handler.setup_event_handlers() # Sets up internal events for agentic tabs (e.g., KR selection) # --- Chained Event Logic (Initial Load & Sync) --- # Outputs for the agentic pipeline run # Ensure these components exist even if the tab is not visible, or handle None gracefully. agentic_pipeline_outputs_list = [ agentic_report_components.get("agentic_report_display_md", gr.update()), agentic_okrs_components.get("key_results_cbg", gr.update()), agentic_okrs_components.get("okr_detail_display_md", gr.update()), orchestration_raw_results_st, selected_key_result_ids_st, # This state is primarily driven by CBG, but pipeline might reset it key_results_for_selection_st, agentic_report_components.get("agentic_pipeline_status_md", gr.update()) ] # Initial Load Sequence: # 1. Get URL token (app.load) # 2. Process token, update dashboard (initial_load_sequence from dashboard_sync_handler) # 3. Refresh analytics graphs (analytics_handler.refresh_analytics_graphs_ui) # 4. Run agentic pipeline (agentic_handler.run_agentic_pipeline_autonomously_on_update) initial_load_event = org_urn_display.change( # Triggers after app.load populates org_urn_display fn=dashboard_sync_handler.initial_load_sequence, inputs=[url_user_token_display, org_urn_display, token_state], outputs=[ status_box, token_state, dashboard_sync_components['sync_data_btn'], dashboard_sync_components['dashboard_display_html'] ], show_progress="full" # For the initial data processing part ) # Chain analytics refresh after initial load initial_load_event.then( fn=analytics_handler.refresh_analytics_graphs_ui, inputs=[ token_state, analytics_components['date_filter_selector'], analytics_components['custom_start_date_picker'], analytics_components['custom_end_date_picker'] # chat_histories_st is accessed via self.chat_histories_st in the handler ], outputs=analytics_handler._get_graph_refresh_outputs_list(), # Get the list of output components show_progress="full" # For graph generation ) # Chain agentic pipeline run after initial analytics refresh (if modules loaded) if APP_AGENTIC_MODULES_LOADED and agentic_handler: initial_load_event.then( # Chaining from initial_load_event ensures it uses the updated token_state fn=agentic_handler.run_agentic_pipeline_autonomously_on_update, inputs=[token_state], # Depends on the updated token_state outputs=agentic_pipeline_outputs_list, show_progress="minimal" # For agentic pipeline ) # Sync Data Sequence: # 1. sync_all_linkedin_data_orchestrator # 2. process_and_store_bubble_token (to update state based on sync results) # 3. display_main_dashboard # 4. refresh_analytics_graphs_ui # 5. run_agentic_pipeline_autonomously_on_update sync_event_part1 = dashboard_sync_components['sync_data_btn'].click( fn=sync_all_linkedin_data_orchestrator, # From services.sync_logic inputs=[token_state], outputs=[dashboard_sync_components['sync_status_html_output'], token_state], show_progress="full" # For the main sync operation ) sync_event_part2 = sync_event_part1.then( fn=process_and_store_bubble_token, # From services.state_manager inputs=[url_user_token_display, org_urn_display, token_state], # Uses the updated token_state from part1 outputs=[status_box, token_state, dashboard_sync_components['sync_data_btn']], show_progress=False # Quick state update ) # Chain agentic pipeline run after sync and token processing (if modules loaded) if APP_AGENTIC_MODULES_LOADED and agentic_handler: sync_event_part2.then( fn=agentic_handler.run_agentic_pipeline_autonomously_on_update, inputs=[token_state], # Uses the token_state updated by process_and_store_bubble_token outputs=agentic_pipeline_outputs_list, show_progress="minimal" ) sync_event_part3 = sync_event_part2.then( # Continues from token processing fn=display_main_dashboard, # From ui.ui_generators inputs=[token_state], # Uses the updated token_state outputs=[dashboard_sync_components['dashboard_display_html']], show_progress=False # Quick UI update ) # Chain analytics refresh after dashboard update post-sync sync_event_part3.then( fn=analytics_handler.refresh_analytics_graphs_ui, inputs=[ token_state, analytics_components['date_filter_selector'], analytics_components['custom_start_date_picker'], analytics_components['custom_end_date_picker'] ], outputs=analytics_handler._get_graph_refresh_outputs_list(), show_progress="full" # For graph generation after sync ) # --- Launch --- if __name__ == "__main__": # Environment variable checks (optional but good practice) if not os.environ.get(LINKEDIN_CLIENT_ID_ENV_VAR): logging.warning(f"ATTENZIONE: '{LINKEDIN_CLIENT_ID_ENV_VAR}' non impostata.") if not all(os.environ.get(var) for var in [BUBBLE_APP_NAME_ENV_VAR, BUBBLE_API_KEY_PRIVATE_ENV_VAR, BUBBLE_API_ENDPOINT_ENV_VAR]): logging.warning("ATTENZIONE: Variabili Bubble non impostate.") if not APP_AGENTIC_MODULES_LOADED: logging.warning("CRITICAL: Agentic pipeline modules failed to load. Agentic tabs (3 and 4) will be non-functional or hidden.") if not os.environ.get("GEMINI_API_KEY") and APP_AGENTIC_MODULES_LOADED: logging.warning("ATTENZIONE: 'GEMINI_API_KEY' non impostata. La pipeline AI per le tab 3 e 4 potrebbe non funzionare.") try: logging.info(f"Matplotlib: {matplotlib.__version__}, Backend: {matplotlib.get_backend()}") except ImportError: logging.warning("Matplotlib non trovato.") app.launch(server_name="0.0.0.0", server_port=7860, debug=True)