LinkedinMonitor / app.py
GuglielmoTor's picture
Update app.py
b30e5c5 verified
raw
history blame
13.4 kB
# 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)