Spaces:
Running
Running
# 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 services.dashboard_sync_handlers import DashboardSyncHandlers | |
from services.analytics_handlers import AnalyticsHandlers | |
from services.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_sync_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) | |