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 | |
import time # For profiling if needed | |
from datetime import datetime, timedelta # Added timedelta | |
import numpy as np | |
from collections import OrderedDict, defaultdict # To maintain section order and for OKR processing | |
import asyncio # For async operations | |
# --- Module Imports --- | |
from gradio_utils import get_url_user_token | |
# Functions from newly created/refactored modules | |
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) | |
from state_manager import process_and_store_bubble_token | |
from sync_logic import sync_all_linkedin_data_orchestrator | |
from ui_generators import ( | |
display_main_dashboard, | |
run_mentions_tab_display, | |
run_follower_stats_tab_display, | |
build_analytics_tab_plot_area, # EXPECTED TO RETURN: plot_ui_objects, section_titles_map | |
BOMB_ICON, EXPLORE_ICON, FORMULA_ICON, ACTIVE_ICON | |
) | |
from analytics_plot_generator import update_analytics_plots_figures, create_placeholder_plot | |
from formulas import PLOT_FORMULAS | |
# --- EXISTING CHATBOT MODULE IMPORTS --- | |
from chatbot_prompts import get_initial_insight_prompt_and_suggestions # MODIFIED IMPORT | |
from chatbot_handler import generate_llm_response | |
# --- END EXISTING CHATBOT MODULE IMPORTS --- | |
# --- NEW AGENTIC PIPELINE IMPORTS --- | |
try: | |
from run_agentic_pipeline import run_full_analytics_orchestration | |
from insights_ui_generator import ( | |
format_report_to_markdown, | |
extract_key_results_for_selection, | |
format_single_okr_for_display | |
) | |
AGENTIC_MODULES_LOADED = True | |
except ImportError as e: | |
logging.error(f"Could not import agentic pipeline modules: {e}. Tabs 5 and 6 will be disabled.") | |
AGENTIC_MODULES_LOADED = False | |
# Define placeholder functions if modules are not loaded to avoid NameErrors | |
async def run_full_analytics_orchestration(*args, **kwargs): return None | |
def format_report_to_markdown(report_string): return "Agentic modules not loaded. Report unavailable." | |
def extract_key_results_for_selection(okrs_dict): return [] | |
def format_single_okr_for_display(okr_data, **kwargs): return "Agentic modules not loaded. OKR display unavailable." | |
# Configure logging | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(message)s') | |
# 1. Set Vertex AI usage preference (if applicable) | |
os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "False" | |
# 2. Get your API key from your chosen environment variable name | |
# Replace "YOUR_GEMINI_API_KEY_ENV_NAME" with the actual name you use, e.g., "GEMINI_API_KEY" or "GOOGLE_API_KEY" | |
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 YOUR_GEMINI_API_KEY_ENV_NAME.") | |
else: | |
logging.error(f"CRITICAL ERROR: The API key environment variable 'YOUR_GEMINI_API_KEY_ENV_NAME' was not found. The application may not function correctly.") | |
# --- Gradio UI Blocks --- | |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"), | |
title="LinkedIn Organization Dashboard") as app: | |
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 existing analytics tab chatbot | |
chat_histories_st = gr.State({}) | |
current_chat_plot_id_st = gr.State(None) | |
plot_data_for_chatbot_st = gr.State({}) | |
# --- NEW STATES FOR AGENTIC PIPELINE --- | |
orchestration_raw_results_st = gr.State(None) | |
# Stores list of dicts: {'okr_index', 'kr_index', 'okr_objective', 'kr_description', 'unique_kr_id'} | |
key_results_for_selection_st = gr.State([]) | |
# Stores list of unique_kr_id strings that are selected by the user | |
selected_key_result_ids_st = gr.State([]) | |
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) | |
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) | |
def initial_load_sequence(url_token, org_urn_val, current_state): | |
status_msg, new_state, btn_update = process_and_store_bubble_token(url_token, org_urn_val, current_state) | |
dashboard_content = display_main_dashboard(new_state) | |
# Initial call to populate analytics plots with default "Sempre" | |
# This will now also trigger the agentic pipeline for the first time. | |
# However, the outputs for agentic tabs are handled by apply_filter_btn.click and sync.then | |
# So, we don't need to return agentic UI updates from here directly. | |
return status_msg, new_state, btn_update, dashboard_content | |
with gr.Tabs() as tabs: | |
with gr.TabItem("1οΈβ£ Dashboard & Sync", id="tab_dashboard_sync"): | |
gr.Markdown("Il sistema controlla i dati esistenti da Bubble. 'Sincronizza' si attiva se sono necessari nuovi dati.") | |
sync_data_btn = gr.Button("π Sincronizza Dati LinkedIn", variant="primary", visible=False, interactive=False) | |
sync_status_html_output = gr.HTML("<p style='text-align:center;'>Stato sincronizzazione...</p>") | |
dashboard_display_html = gr.HTML("<p style='text-align:center;'>Caricamento dashboard...</p>") | |
org_urn_display.change( | |
fn=initial_load_sequence, | |
inputs=[url_user_token_display, org_urn_display, token_state], | |
outputs=[status_box, token_state, sync_data_btn, dashboard_display_html], | |
show_progress="full" | |
) | |
with gr.TabItem("2οΈβ£ Analisi", id="tab_analytics"): | |
gr.Markdown("## π Analisi Performance LinkedIn") | |
gr.Markdown("Seleziona un intervallo di date. Clicca i pulsanti (π£ Insights, Ζ Formula, π§ Esplora) su un grafico per azioni.") | |
analytics_status_md = gr.Markdown("Stato analisi...") | |
with gr.Row(): | |
date_filter_selector = gr.Radio( | |
["Sempre", "Ultimi 7 Giorni", "Ultimi 30 Giorni", "Intervallo Personalizzato"], | |
label="Seleziona Intervallo Date", value="Sempre", scale=3 | |
) | |
with gr.Column(scale=2): | |
custom_start_date_picker = gr.DateTime(label="Data Inizio", visible=False, include_time=False, type="datetime") | |
custom_end_date_picker = gr.DateTime(label="Data Fine", visible=False, include_time=False, type="datetime") | |
apply_filter_btn = gr.Button("π Applica Filtro & Aggiorna Analisi", variant="primary") | |
def toggle_custom_date_pickers(selection): | |
is_custom = selection == "Intervallo Personalizzato" | |
return gr.update(visible=is_custom), gr.update(visible=is_custom) | |
date_filter_selector.change( | |
fn=toggle_custom_date_pickers, | |
inputs=[date_filter_selector], | |
outputs=[custom_start_date_picker, custom_end_date_picker] | |
) | |
plot_configs = [ | |
{"label": "Numero di Follower nel Tempo", "id": "followers_count", "section": "Dinamiche dei Follower"}, | |
{"label": "Tasso di Crescita Follower", "id": "followers_growth_rate", "section": "Dinamiche dei Follower"}, | |
{"label": "Follower per LocalitΓ ", "id": "followers_by_location", "section": "Demografia Follower"}, | |
{"label": "Follower per Ruolo (Funzione)", "id": "followers_by_role", "section": "Demografia Follower"}, | |
{"label": "Follower per Settore", "id": "followers_by_industry", "section": "Demografia Follower"}, | |
{"label": "Follower per AnzianitΓ ", "id": "followers_by_seniority", "section": "Demografia Follower"}, | |
{"label": "Tasso di Engagement nel Tempo", "id": "engagement_rate", "section": "Approfondimenti Performance Post"}, | |
{"label": "Copertura nel Tempo", "id": "reach_over_time", "section": "Approfondimenti Performance Post"}, | |
{"label": "Visualizzazioni nel Tempo", "id": "impressions_over_time", "section": "Approfondimenti Performance Post"}, | |
{"label": "Reazioni (Like) nel Tempo", "id": "likes_over_time", "section": "Approfondimenti Performance Post"}, | |
{"label": "Click nel Tempo", "id": "clicks_over_time", "section": "Engagement Dettagliato Post nel Tempo"}, | |
{"label": "Condivisioni nel Tempo", "id": "shares_over_time", "section": "Engagement Dettagliato Post nel Tempo"}, | |
{"label": "Commenti nel Tempo", "id": "comments_over_time", "section": "Engagement Dettagliato Post nel Tempo"}, | |
{"label": "Ripartizione Commenti per Sentiment", "id": "comments_sentiment", "section": "Engagement Dettagliato Post nel Tempo"}, | |
{"label": "Frequenza Post", "id": "post_frequency_cs", "section": "Analisi Strategia Contenuti"}, | |
{"label": "Ripartizione Contenuti per Formato", "id": "content_format_breakdown_cs", "section": "Analisi Strategia Contenuti"}, | |
{"label": "Ripartizione Contenuti per Argomenti", "id": "content_topic_breakdown_cs", "section": "Analisi Strategia Contenuti"}, | |
{"label": "Volume Menzioni nel Tempo (Dettaglio)", "id": "mention_analysis_volume", "section": "Analisi Menzioni (Dettaglio)"}, | |
{"label": "Ripartizione Menzioni per Sentiment (Dettaglio)", "id": "mention_analysis_sentiment", "section": "Analisi Menzioni (Dettaglio)"} | |
] | |
assert len(plot_configs) == 19, "Mancata corrispondenza in plot_configs e grafici attesi." | |
unique_ordered_sections = list(OrderedDict.fromkeys(pc["section"] for pc in plot_configs)) | |
num_unique_sections = len(unique_ordered_sections) | |
active_panel_action_state = gr.State(None) | |
explored_plot_id_state = gr.State(None) | |
plot_ui_objects = {} | |
section_titles_map = {} | |
with gr.Row(equal_height=False): | |
with gr.Column(scale=8) as plots_area_col: | |
ui_elements_tuple = build_analytics_tab_plot_area(plot_configs) | |
if isinstance(ui_elements_tuple, tuple) and len(ui_elements_tuple) == 2: | |
plot_ui_objects, section_titles_map = ui_elements_tuple | |
if not all(sec_name in section_titles_map for sec_name in unique_ordered_sections): | |
logging.error("section_titles_map from build_analytics_tab_plot_area is incomplete.") | |
for sec_name in unique_ordered_sections: | |
if sec_name not in section_titles_map: | |
section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)") | |
else: | |
logging.error("build_analytics_tab_plot_area did not return a tuple of (plot_ui_objects, section_titles_map).") | |
plot_ui_objects = ui_elements_tuple if isinstance(ui_elements_tuple, dict) else {} | |
for sec_name in unique_ordered_sections: | |
section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)") | |
with gr.Column(scale=4, visible=False) as global_actions_column_ui: | |
gr.Markdown("### π‘ Azioni Contestuali Grafico") | |
insights_chatbot_ui = gr.Chatbot( | |
label="Chat Insights", type="messages", height=450, | |
bubble_full_width=False, visible=False, show_label=False, | |
placeholder="L'analisi AI del grafico apparirΓ qui. Fai domande di approfondimento!" | |
) | |
insights_chat_input_ui = gr.Textbox( | |
label="La tua domanda:", placeholder="Chiedi all'AI riguardo a questo grafico...", | |
lines=2, visible=False, show_label=False | |
) | |
with gr.Row(visible=False) as insights_suggestions_row_ui: | |
insights_suggestion_1_btn = gr.Button(value="Suggerimento 1", size="sm", min_width=50) | |
insights_suggestion_2_btn = gr.Button(value="Suggerimento 2", size="sm", min_width=50) | |
insights_suggestion_3_btn = gr.Button(value="Suggerimento 3", size="sm", min_width=50) | |
formula_display_markdown_ui = gr.Markdown( | |
"I dettagli sulla formula/metodologia appariranno qui.", visible=False | |
) | |
formula_close_hint_md = gr.Markdown( # Component for the hint's visibility | |
"<p style='font-size:0.9em; text-align:center; margin-top:10px;'><em>Click the active Ζ button on the plot again to close this panel.</em></p>", | |
visible=False | |
) | |
# --- ASYNC HANDLERS FOR ANALYTICS TAB --- | |
async def handle_panel_action( | |
plot_id_clicked: str, action_type: str, current_active_action_from_state: dict, | |
current_chat_histories: dict, current_chat_plot_id: str, | |
current_plot_data_for_chatbot: dict, current_explored_plot_id: str | |
): | |
logging.info(f"Panel Action: '{action_type}' for plot '{plot_id_clicked}'. Active: {current_active_action_from_state}, Explored: {current_explored_plot_id}") | |
clicked_plot_config = next((p for p in plot_configs if p["id"] == plot_id_clicked), None) | |
if not clicked_plot_config: | |
logging.error(f"Config not found for plot_id {plot_id_clicked}") | |
num_plots = len(plot_configs) | |
error_list_len = 15 + (4 * num_plots) + num_unique_sections | |
error_list = [gr.update()] * error_list_len | |
error_list[11] = current_active_action_from_state | |
error_list[12] = current_chat_plot_id | |
error_list[13] = current_chat_histories | |
error_list[14] = current_explored_plot_id | |
return error_list | |
clicked_plot_label = clicked_plot_config["label"] | |
clicked_plot_section = clicked_plot_config["section"] | |
hypothetical_new_active_state = {"plot_id": plot_id_clicked, "type": action_type} | |
is_toggling_off = current_active_action_from_state == hypothetical_new_active_state | |
action_col_visible_update = gr.update(visible=False) | |
insights_chatbot_visible_update, insights_chat_input_visible_update, insights_suggestions_row_visible_update = gr.update(visible=False), gr.update(visible=False), gr.update(visible=False) | |
formula_display_visible_update = gr.update(visible=False) | |
formula_close_hint_visible_update = gr.update(visible=False) | |
chatbot_content_update, s1_upd, s2_upd, s3_upd, formula_content_update = gr.update(), gr.update(), gr.update(), gr.update(), gr.update() | |
new_active_action_state_to_set, new_current_chat_plot_id = None, current_chat_plot_id | |
updated_chat_histories, new_explored_plot_id_to_set = current_chat_histories, current_explored_plot_id | |
generated_panel_vis_updates = [] | |
generated_bomb_btn_updates = [] | |
generated_formula_btn_updates = [] | |
generated_explore_btn_updates = [] | |
section_title_vis_updates = [gr.update()] * num_unique_sections | |
if is_toggling_off: | |
new_active_action_state_to_set = None | |
action_col_visible_update = gr.update(visible=False) | |
logging.info(f"Toggling OFF panel {action_type} for {plot_id_clicked}.") | |
for _ in plot_configs: | |
generated_bomb_btn_updates.append(gr.update(value=BOMB_ICON)) | |
generated_formula_btn_updates.append(gr.update(value=FORMULA_ICON)) | |
if current_explored_plot_id: | |
explored_cfg = next((p for p in plot_configs if p["id"] == current_explored_plot_id), None) | |
explored_sec = explored_cfg["section"] if explored_cfg else None | |
for i, sec_name in enumerate(unique_ordered_sections): | |
section_title_vis_updates[i] = gr.update(visible=(sec_name == explored_sec)) | |
for cfg in plot_configs: | |
is_exp = (cfg["id"] == current_explored_plot_id) | |
generated_panel_vis_updates.append(gr.update(visible=is_exp)) | |
generated_explore_btn_updates.append(gr.update(value=ACTIVE_ICON if is_exp else EXPLORE_ICON)) | |
else: | |
for i in range(num_unique_sections): section_title_vis_updates[i] = gr.update(visible=True) | |
for _ in plot_configs: | |
generated_panel_vis_updates.append(gr.update(visible=True)) | |
generated_explore_btn_updates.append(gr.update(value=EXPLORE_ICON)) | |
if action_type == "insights": new_current_chat_plot_id = None | |
else: # Toggling ON a new action or switching actions | |
new_active_action_state_to_set = hypothetical_new_active_state | |
action_col_visible_update = gr.update(visible=True) | |
new_explored_plot_id_to_set = None | |
logging.info(f"Toggling ON panel {action_type} for {plot_id_clicked}. Cancelling explore view if any.") | |
for i, sec_name in enumerate(unique_ordered_sections): | |
section_title_vis_updates[i] = gr.update(visible=(sec_name == clicked_plot_section)) | |
for cfg in plot_configs: | |
generated_panel_vis_updates.append(gr.update(visible=(cfg["id"] == plot_id_clicked))) | |
generated_explore_btn_updates.append(gr.update(value=EXPLORE_ICON)) | |
for cfg_btn in plot_configs: | |
is_act_ins = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "insights"} | |
is_act_for = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "formula"} | |
generated_bomb_btn_updates.append(gr.update(value=ACTIVE_ICON if is_act_ins else BOMB_ICON)) | |
generated_formula_btn_updates.append(gr.update(value=ACTIVE_ICON if is_act_for else FORMULA_ICON)) | |
if action_type == "insights": | |
insights_chatbot_visible_update, insights_chat_input_visible_update, insights_suggestions_row_visible_update = gr.update(visible=True), gr.update(visible=True), gr.update(visible=True) | |
new_current_chat_plot_id = plot_id_clicked | |
history = current_chat_histories.get(plot_id_clicked, []) | |
summary = current_plot_data_for_chatbot.get(plot_id_clicked, f"No summary for '{clicked_plot_label}'.") | |
if not history: | |
prompt, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary) | |
llm_hist = [{"role": "user", "content": prompt}] | |
resp = await generate_llm_response(prompt, plot_id_clicked, clicked_plot_label, llm_hist, summary) # This is your existing LLM call | |
history = [{"role": "assistant", "content": resp}] | |
updated_chat_histories = {**current_chat_histories, plot_id_clicked: history} | |
else: | |
_, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary) | |
chatbot_content_update = gr.update(value=history) | |
s1_upd,s2_upd,s3_upd = gr.update(value=sugg[0] if sugg else "N/A"),gr.update(value=sugg[1] if len(sugg)>1 else "N/A"),gr.update(value=sugg[2] if len(sugg)>2 else "N/A") | |
elif action_type == "formula": | |
formula_display_visible_update = gr.update(visible=True) | |
formula_close_hint_visible_update = gr.update(visible=True) | |
f_key = PLOT_ID_TO_FORMULA_KEY_MAP.get(plot_id_clicked) | |
f_text = f"**Formula/Methodology for: {clicked_plot_label}** (ID: `{plot_id_clicked}`)\n\n" | |
if f_key and f_key in PLOT_FORMULAS: | |
f_data = PLOT_FORMULAS[f_key] | |
f_text += f"### {f_data['title']}\n\n{f_data['description']}\n\n**Calculation:**\n" + "\n".join([f"- {s}" for s in f_data['calculation_steps']]) | |
else: f_text += "(No detailed formula information found.)" | |
formula_content_update = gr.update(value=f_text) | |
new_current_chat_plot_id = None | |
final_updates = [ | |
action_col_visible_update, insights_chatbot_visible_update, chatbot_content_update, | |
insights_chat_input_visible_update, insights_suggestions_row_visible_update, | |
s1_upd, s2_upd, s3_upd, formula_display_visible_update, formula_content_update, | |
formula_close_hint_visible_update, # Corrected from formula_close_hint_md | |
new_active_action_state_to_set, new_current_chat_plot_id, updated_chat_histories, | |
new_explored_plot_id_to_set | |
] | |
final_updates.extend(generated_panel_vis_updates) | |
final_updates.extend(generated_bomb_btn_updates) | |
final_updates.extend(generated_formula_btn_updates) | |
final_updates.extend(generated_explore_btn_updates) | |
final_updates.extend(section_title_vis_updates) | |
logging.debug(f"handle_panel_action returning {len(final_updates)} updates. Expected {15 + 4*len(plot_configs) + num_unique_sections}.") | |
return final_updates | |
async def handle_chat_message_submission(user_message: str, current_plot_id: str, chat_histories: dict, current_plot_data_for_chatbot: dict ): | |
if not current_plot_id or not user_message.strip(): | |
current_history_for_plot = chat_histories.get(current_plot_id, []) | |
if not isinstance(current_history_for_plot, list): current_history_for_plot = [] | |
yield current_history_for_plot, gr.update(value=""), chat_histories; return | |
cfg = next((p for p in plot_configs if p["id"] == current_plot_id), None) | |
lbl = cfg["label"] if cfg else "Selected Plot" | |
summary = current_plot_data_for_chatbot.get(current_plot_id, f"No summary for '{lbl}'.") | |
hist_for_plot = chat_histories.get(current_plot_id, []) | |
if not isinstance(hist_for_plot, list): hist_for_plot = [] | |
hist = hist_for_plot.copy() + [{"role": "user", "content": user_message}] | |
yield hist, gr.update(value=""), chat_histories | |
resp = await generate_llm_response(user_message, current_plot_id, lbl, hist, summary) # Existing LLM | |
hist.append({"role": "assistant", "content": resp}) | |
updated_chat_histories = {**chat_histories, current_plot_id: hist} | |
yield hist, "", updated_chat_histories | |
async def handle_suggested_question_click(suggestion_text: str, current_plot_id: str, chat_histories: dict, current_plot_data_for_chatbot: dict): | |
if not current_plot_id or not suggestion_text.strip() or suggestion_text == "N/A": | |
current_history_for_plot = chat_histories.get(current_plot_id, []) | |
if not isinstance(current_history_for_plot, list): current_history_for_plot = [] | |
yield current_history_for_plot, gr.update(value=""), chat_histories; return | |
async for update_chunk in handle_chat_message_submission(suggestion_text, current_plot_id, chat_histories, current_plot_data_for_chatbot): | |
yield update_chunk | |
def handle_explore_click(plot_id_clicked, current_explored_plot_id_from_state, current_active_panel_action_state): | |
# This function remains synchronous as per original | |
logging.info(f"Explore Click: Plot '{plot_id_clicked}'. Current Explored: {current_explored_plot_id_from_state}. Active Panel: {current_active_panel_action_state}") | |
num_plots = len(plot_configs) | |
if not plot_ui_objects: | |
logging.error("plot_ui_objects not populated for handle_explore_click.") | |
error_list_len = 4 + (4 * num_plots) + num_unique_sections | |
error_list = [gr.update()] * error_list_len | |
error_list[0] = current_explored_plot_id_from_state | |
error_list[2] = current_active_panel_action_state | |
return error_list | |
new_explored_id_to_set = None | |
is_toggling_off_explore = (plot_id_clicked == current_explored_plot_id_from_state) | |
action_col_upd = gr.update() | |
new_active_panel_state_upd = current_active_panel_action_state | |
formula_hint_upd = gr.update(visible=False) | |
panel_vis_updates = [] | |
explore_btns_updates = [] | |
bomb_btns_updates = [] | |
formula_btns_updates = [] | |
section_title_vis_updates = [gr.update()] * num_unique_sections | |
clicked_cfg = next((p for p in plot_configs if p["id"] == plot_id_clicked), None) | |
sec_of_clicked = clicked_cfg["section"] if clicked_cfg else None | |
if is_toggling_off_explore: | |
new_explored_id_to_set = None | |
logging.info(f"Stopping explore for {plot_id_clicked}. All plots/sections to be visible.") | |
for i in range(num_unique_sections): section_title_vis_updates[i] = gr.update(visible=True) | |
for _ in plot_configs: | |
panel_vis_updates.append(gr.update(visible=True)) | |
explore_btns_updates.append(gr.update(value=EXPLORE_ICON)) | |
bomb_btns_updates.append(gr.update()) | |
formula_btns_updates.append(gr.update()) | |
else: | |
new_explored_id_to_set = plot_id_clicked | |
logging.info(f"Exploring {plot_id_clicked}. Hiding other plots/sections.") | |
for i, sec_name in enumerate(unique_ordered_sections): | |
section_title_vis_updates[i] = gr.update(visible=(sec_name == sec_of_clicked)) | |
for cfg in plot_configs: | |
is_target = (cfg["id"] == new_explored_id_to_set) | |
panel_vis_updates.append(gr.update(visible=is_target)) | |
explore_btns_updates.append(gr.update(value=ACTIVE_ICON if is_target else EXPLORE_ICON)) | |
if current_active_panel_action_state: | |
logging.info("Closing active insight/formula panel due to explore click.") | |
action_col_upd = gr.update(visible=False) | |
new_active_panel_state_upd = None | |
formula_hint_upd = gr.update(visible=False) | |
# Reset bomb and formula buttons to non-active | |
bomb_btns_updates = [gr.update(value=BOMB_ICON) for _ in plot_configs] | |
formula_btns_updates = [gr.update(value=FORMULA_ICON) for _ in plot_configs] | |
else: # No active panel, so no need to reset bomb/formula buttons beyond what explore does | |
bomb_btns_updates = [gr.update() for _ in plot_configs] | |
formula_btns_updates = [gr.update() for _ in plot_configs] | |
final_explore_updates = [ | |
new_explored_id_to_set, action_col_upd, new_active_panel_state_upd, formula_hint_upd | |
] | |
final_explore_updates.extend(panel_vis_updates) | |
final_explore_updates.extend(explore_btns_updates) | |
final_explore_updates.extend(bomb_btns_updates) | |
final_explore_updates.extend(formula_btns_updates) | |
final_explore_updates.extend(section_title_vis_updates) | |
logging.debug(f"handle_explore_click returning {len(final_explore_updates)} updates. Expected {4 + 4*len(plot_configs) + num_unique_sections}.") | |
return final_explore_updates | |
_base_action_panel_ui_outputs = [ | |
global_actions_column_ui, insights_chatbot_ui, insights_chatbot_ui, | |
insights_chat_input_ui, insights_suggestions_row_ui, | |
insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn, | |
formula_display_markdown_ui, formula_display_markdown_ui, | |
formula_close_hint_md | |
] | |
_action_panel_state_outputs = [active_panel_action_state, current_chat_plot_id_st, chat_histories_st, explored_plot_id_state] | |
action_panel_outputs_list = _base_action_panel_ui_outputs + _action_panel_state_outputs | |
action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("panel_component", gr.update()) for pc in plot_configs]) | |
action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("bomb_button", gr.update()) for pc in plot_configs]) | |
action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs]) | |
action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("explore_button", gr.update()) for pc in plot_configs]) | |
action_panel_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections]) | |
_explore_base_outputs = [explored_plot_id_state, global_actions_column_ui, active_panel_action_state, formula_close_hint_md] | |
explore_outputs_list = _explore_base_outputs | |
explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("panel_component", gr.update()) for pc in plot_configs]) | |
explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("explore_button", gr.update()) for pc in plot_configs]) | |
explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("bomb_button", gr.update()) for pc in plot_configs]) | |
explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs]) | |
explore_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections]) | |
action_click_inputs = [active_panel_action_state, chat_histories_st, current_chat_plot_id_st, plot_data_for_chatbot_st, explored_plot_id_state] | |
explore_click_inputs = [explored_plot_id_state, active_panel_action_state] | |
def create_panel_action_handler(p_id, action_type_str): | |
async def _handler(curr_active_val, curr_chats_val, curr_chat_pid, curr_plot_data, curr_explored_id): | |
return await handle_panel_action(p_id, action_type_str, curr_active_val, curr_chats_val, curr_chat_pid, curr_plot_data, curr_explored_id) | |
return _handler | |
for config_item in plot_configs: | |
plot_id = config_item["id"] | |
if plot_id in plot_ui_objects: | |
ui_obj = plot_ui_objects[plot_id] | |
if ui_obj.get("bomb_button"): | |
ui_obj["bomb_button"].click(fn=create_panel_action_handler(plot_id, "insights"), inputs=action_click_inputs, outputs=action_panel_outputs_list, api_name=f"action_insights_{plot_id}") | |
if ui_obj.get("formula_button"): | |
ui_obj["formula_button"].click(fn=create_panel_action_handler(plot_id, "formula"), inputs=action_click_inputs, outputs=action_panel_outputs_list, api_name=f"action_formula_{plot_id}") | |
if ui_obj.get("explore_button"): | |
ui_obj["explore_button"].click( | |
fn=lambda current_explored_val, current_active_panel_val, p_id=plot_id: handle_explore_click(p_id, current_explored_val, current_active_panel_val), | |
inputs=explore_click_inputs, | |
outputs=explore_outputs_list, | |
api_name=f"action_explore_{plot_id}" | |
) | |
else: logging.warning(f"UI object for plot_id '{plot_id}' not found for click handlers.") | |
chat_submission_outputs = [insights_chatbot_ui, insights_chat_input_ui, chat_histories_st] | |
chat_submission_inputs = [insights_chat_input_ui, current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st] | |
insights_chat_input_ui.submit(fn=handle_chat_message_submission, inputs=chat_submission_inputs, outputs=chat_submission_outputs, api_name="submit_chat_message") | |
suggestion_click_inputs_base = [current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st] | |
insights_suggestion_1_btn.click(fn=handle_suggested_question_click, inputs=[insights_suggestion_1_btn] + suggestion_click_inputs_base, outputs=chat_submission_outputs, api_name="click_suggestion_1") | |
insights_suggestion_2_btn.click(fn=handle_suggested_question_click, inputs=[insights_suggestion_2_btn] + suggestion_click_inputs_base, outputs=chat_submission_outputs, api_name="click_suggestion_2") | |
insights_suggestion_3_btn.click(fn=handle_suggested_question_click, inputs=[insights_suggestion_3_btn] + suggestion_click_inputs_base, outputs=chat_submission_outputs, api_name="click_suggestion_3") | |
with gr.TabItem("3οΈβ£ Menzioni", id="tab_mentions"): | |
refresh_mentions_display_btn = gr.Button("π Aggiorna Visualizzazione Menzioni", variant="secondary") | |
mentions_html = gr.HTML("Dati menzioni...") | |
mentions_sentiment_dist_plot = gr.Plot(label="Distribuzione Sentiment Menzioni") | |
refresh_mentions_display_btn.click( | |
fn=run_mentions_tab_display, inputs=[token_state], | |
outputs=[mentions_html, mentions_sentiment_dist_plot], | |
show_progress="full" | |
) | |
with gr.TabItem("4οΈβ£ Statistiche Follower", id="tab_follower_stats"): | |
refresh_follower_stats_btn = gr.Button("π Aggiorna Visualizzazione Statistiche Follower", variant="secondary") | |
follower_stats_html = gr.HTML("Statistiche follower...") | |
with gr.Row(): | |
fs_plot_monthly_gains = gr.Plot(label="Guadagni Mensili Follower") | |
with gr.Row(): | |
fs_plot_seniority = gr.Plot(label="Follower per AnzianitΓ (Top 10 Organici)") | |
fs_plot_industry = gr.Plot(label="Follower per Settore (Top 10 Organici)") | |
refresh_follower_stats_btn.click( | |
fn=run_follower_stats_tab_display, inputs=[token_state], | |
outputs=[follower_stats_html, fs_plot_monthly_gains, fs_plot_seniority, fs_plot_industry], | |
show_progress="full" | |
) | |
# --- NEW TABS FOR AGENTIC PIPELINE RESULTS --- | |
with gr.TabItem("5οΈβ£ Agentic Analysis Report", id="tab_agentic_report", visible=AGENTIC_MODULES_LOADED): | |
gr.Markdown("## π€ Comprehensive Analysis Report (AI Generated)") | |
gr.Markdown("This report is generated by an AI agent based on the selected date range. Please review critically.") | |
agentic_report_display_md = gr.Markdown("Apply a date filter in Tab 2 to generate the report.") | |
if not AGENTIC_MODULES_LOADED: | |
gr.Markdown("π΄ **Error:** Agentic pipeline modules could not be loaded. This tab is disabled.") | |
with gr.TabItem("6οΈβ£ Agentic OKRs & Tasks", id="tab_agentic_okrs", visible=AGENTIC_MODULES_LOADED): | |
gr.Markdown("## π― AI Generated OKRs and Actionable Tasks") | |
gr.Markdown("Based on the analysis, the AI agent has proposed the following Objectives and Key Results (OKRs), along with actionable tasks. Select Key Results you want to focus on to see detailed tasks.") | |
if not AGENTIC_MODULES_LOADED: | |
gr.Markdown("π΄ **Error:** Agentic pipeline modules could not be loaded. This tab is disabled.") | |
with gr.Row(): | |
with gr.Column(scale=1): | |
gr.Markdown("### Suggested Key Results") | |
key_results_cbg = gr.CheckboxGroup( | |
label="Select Key Results", | |
choices=[], # Will be populated dynamically | |
value=[], | |
interactive=True | |
) | |
with gr.Column(scale=3): | |
gr.Markdown("### Detailed OKRs and Tasks for Selected Key Results") | |
okr_detail_display_md = gr.Markdown("Select Key Results from the left to see details, or apply a date filter in Tab 2 to generate OKRs.") | |
def update_okr_display_on_selection(selected_kr_unique_ids: list, raw_orchestration_results: dict, all_krs_for_selection: list): | |
""" | |
Updates the Markdown display for OKRs based on selected Key Results. | |
If no KRs are selected, it might show all, or a prompt. | |
""" | |
if not raw_orchestration_results or not AGENTIC_MODULES_LOADED: | |
return gr.update(value="No orchestration data available or agentic modules not loaded. Apply a date filter first.") | |
actionable_okrs_dict = raw_orchestration_results.get("actionable_okrs_and_tasks") | |
if not actionable_okrs_dict or not isinstance(actionable_okrs_dict.get("okrs"), list): | |
return gr.update(value="No OKRs found in the orchestration results.") | |
okrs_list = actionable_okrs_dict["okrs"] | |
# Create a mapping from unique_kr_id to (okr_idx, kr_idx) | |
kr_id_to_indices = { | |
kr_info['unique_kr_id']: (kr_info['okr_index'], kr_info['kr_index']) | |
for kr_info in all_krs_for_selection | |
} | |
# Determine which KRs are selected for each OKR | |
# okr_idx -> list of selected kr_indices for that OKR | |
selected_krs_by_okr_idx = defaultdict(list) | |
if selected_kr_unique_ids: # If specific KRs are selected | |
for kr_unique_id in selected_kr_unique_ids: | |
if kr_unique_id in kr_id_to_indices: | |
okr_idx, kr_idx = kr_id_to_indices[kr_unique_id] | |
selected_krs_by_okr_idx[okr_idx].append(kr_idx) | |
output_md_parts = [] | |
if not okrs_list: | |
output_md_parts.append("No OKRs were generated.") | |
else: | |
for okr_idx, okr_data in enumerate(okrs_list): | |
# If specific KRs were selected, only display OKRs that have at least one selected KR, | |
# or if no KRs are selected at all, display all OKRs with all their KRs. | |
accepted_indices_for_this_okr = selected_krs_by_okr_idx.get(okr_idx) # This will be None if no KRs from this OKR are selected | |
if selected_kr_unique_ids: # User has made a selection | |
if accepted_indices_for_this_okr is not None : # Only show this OKR if some of its KRs are selected | |
output_md_parts.append(format_single_okr_for_display( | |
okr_data, | |
accepted_kr_indices=accepted_indices_for_this_okr, | |
okr_main_index=okr_idx | |
)) | |
else: # No KRs selected by user, show all OKRs with all their KRs | |
output_md_parts.append(format_single_okr_for_display( | |
okr_data, | |
accepted_kr_indices=None, # None means show all KRs for this OKR | |
okr_main_index=okr_idx | |
)) | |
if not output_md_parts and selected_kr_unique_ids: # User made selection, but no matching OKRs displayed | |
final_md = "No OKRs match the current Key Result selection, or the selected Key Results do not have detailed tasks." | |
elif not output_md_parts and not selected_kr_unique_ids: # No user selection and no OKRs at all | |
final_md = "No OKRs generated. Please apply a filter in Tab 2." | |
else: | |
final_md = "\n\n---\n\n".join(output_md_parts) | |
return gr.update(value=final_md) | |
if AGENTIC_MODULES_LOADED: | |
key_results_cbg.change( | |
fn=update_okr_display_on_selection, | |
inputs=[key_results_cbg, orchestration_raw_results_st, key_results_for_selection_st], | |
outputs=[okr_detail_display_md] | |
) | |
# --- END NEW TABS --- | |
# --- REFRESH ANALYTICS (NOW ASYNC and includes AGENTIC PIPELINE) --- | |
async def refresh_all_analytics_ui_elements(current_token_state_val, date_filter_val, custom_start_val, custom_end_val, current_chat_histories_val): | |
logging.info("Refreshing all analytics UI elements and resetting actions/chat.") | |
start_time = time.time() | |
# 1. Standard Analytics Plot Generation | |
plot_gen_results = update_analytics_plots_figures(current_token_state_val, date_filter_val, custom_start_val, custom_end_val, plot_configs) | |
status_msg, gen_figs, new_summaries = plot_gen_results[0], plot_gen_results[1:-1], plot_gen_results[-1] | |
all_updates = [status_msg] | |
all_updates.extend(gen_figs if len(gen_figs) == len(plot_configs) else [create_placeholder_plot("Error", f"Fig missing {i}") for i in range(len(plot_configs))]) | |
# UI Resets for Analytics Tab Action Panel | |
all_updates.extend([ | |
gr.update(visible=False), # global_actions_column_ui | |
gr.update(value=[], visible=False), # insights_chatbot_ui (value & visibility) | |
gr.update(value="", visible=False), # insights_chat_input_ui (value & visibility) | |
gr.update(visible=False), # insights_suggestions_row_ui | |
gr.update(value="S1"), gr.update(value="S2"), gr.update(value="S3"), # suggestion_btns | |
gr.update(value="Formula details here.", visible=False), # formula_display_markdown_ui (value & visibility) | |
gr.update(visible=False) # formula_close_hint_md | |
]) | |
# State Resets for Analytics Tab | |
all_updates.extend([ | |
None, # active_panel_action_state | |
None, # current_chat_plot_id_st | |
{}, # chat_histories_st (resetting, new insights will be generated if panel opened) | |
new_summaries # plot_data_for_chatbot_st | |
]) | |
# Plot button and panel visibility resets for Analytics Tab | |
for _ in plot_configs: | |
all_updates.extend([ | |
gr.update(value=BOMB_ICON), | |
gr.update(value=FORMULA_ICON), | |
gr.update(value=EXPLORE_ICON), | |
gr.update(visible=True) # panel_component visibility | |
]) | |
all_updates.append(None) # explored_plot_id_state reset | |
all_updates.extend([gr.update(visible=True)] * num_unique_sections) # section_title visibility reset | |
# 2. Agentic Pipeline Execution | |
agentic_status_update = "Agentic pipeline processing..." | |
# Placeholder for agentic UI updates initially | |
agentic_report_md_update = gr.update(value="Analisi AI in corso...") | |
key_results_cbg_update = gr.update(choices=[], value=[], interactive=False) | |
okr_detail_display_md_update = gr.update(value="Dettagli OKR in corso di generazione...") | |
orchestration_results_update = None | |
selected_krs_update = [] | |
krs_for_selection_update = [] | |
if AGENTIC_MODULES_LOADED: | |
try: | |
logging.info("Starting agentic pipeline...") | |
orchestration_output = await run_full_analytics_orchestration( | |
current_token_state_val, date_filter_val, custom_start_val, custom_end_val | |
) | |
agentic_status_update = "Agentic pipeline completed." | |
logging.info(f"Agentic pipeline finished. Output keys: {orchestration_output.keys() if orchestration_output else 'None'}") | |
if orchestration_output: | |
orchestration_results_update = orchestration_output # Store raw results | |
# Format report for Tab 5 | |
report_str = orchestration_output.get('comprehensive_analysis_report_str') | |
agentic_report_md_update = gr.update(value=format_report_to_markdown(report_str)) | |
# Prepare KRs for Tab 6 | |
actionable_okrs = orchestration_output.get('actionable_okrs_and_tasks') | |
krs_for_ui_selection_list = extract_key_results_for_selection(actionable_okrs) | |
krs_for_selection_update = krs_for_ui_selection_list # Store for later use in event handler | |
kr_choices_for_cbg = [(kr['kr_description'], kr['unique_kr_id']) for kr in krs_for_ui_selection_list] | |
key_results_cbg_update = gr.update(choices=kr_choices_for_cbg, value=[], interactive=True) | |
# Initially display all OKRs in Tab 6 | |
all_okrs_md_parts = [] | |
if actionable_okrs and isinstance(actionable_okrs.get("okrs"), list): | |
for okr_idx, okr_item in enumerate(actionable_okrs["okrs"]): | |
all_okrs_md_parts.append(format_single_okr_for_display(okr_item, accepted_kr_indices=None, okr_main_index=okr_idx)) | |
if not all_okrs_md_parts: | |
okr_detail_display_md_update = gr.update(value="Nessun OKR generato o trovato.") | |
else: | |
okr_detail_display_md_update = gr.update(value="\n\n---\n\n".join(all_okrs_md_parts)) | |
selected_krs_update = [] # Reset selection | |
else: | |
agentic_report_md_update = gr.update(value="Nessun report generato dalla pipeline AI.") | |
key_results_cbg_update = gr.update(choices=[], value=[], interactive=False) | |
okr_detail_display_md_update = gr.update(value="Nessun OKR generato o errore nella pipeline AI.") | |
orchestration_results_update = None | |
selected_krs_update = [] | |
krs_for_selection_update = [] | |
except Exception as e: | |
logging.error(f"Error during agentic pipeline execution: {e}", exc_info=True) | |
agentic_status_update = f"Errore pipeline AI: {e}" | |
agentic_report_md_update = gr.update(value=f"Errore durante la generazione del report AI: {e}") | |
key_results_cbg_update = gr.update(choices=[], value=[], interactive=False) | |
okr_detail_display_md_update = gr.update(value=f"Errore durante la generazione degli OKR AI: {e}") | |
orchestration_results_update = None | |
selected_krs_update = [] | |
krs_for_selection_update = [] | |
else: # AGENTIC_MODULES_LOADED is False | |
agentic_status_update = "Moduli AI non caricati. Pipeline saltata." | |
agentic_report_md_update = gr.update(value="Moduli AI non caricati. Report non disponibile.") | |
key_results_cbg_update = gr.update(choices=[], value=[], interactive=False, visible=False) # Hide if not loaded | |
okr_detail_display_md_update = gr.update(value="Moduli AI non caricati. OKR non disponibili.", visible=False) # Hide | |
orchestration_results_update = None | |
selected_krs_update = [] | |
krs_for_selection_update = [] | |
# Append agentic pipeline updates to all_updates | |
# Order must match apply_filter_and_sync_outputs_list extension | |
all_updates.extend([ | |
agentic_report_md_update, # For agentic_report_display_md | |
key_results_cbg_update, # For key_results_cbg | |
okr_detail_display_md_update, # For okr_detail_display_md | |
orchestration_results_update, # For orchestration_raw_results_st | |
selected_krs_update, # For selected_key_result_ids_st | |
krs_for_selection_update # For key_results_for_selection_st | |
]) | |
end_time = time.time() | |
logging.info(f"Analytics and Agentic refresh took {end_time - start_time:.2f} seconds.") | |
logging.info(f"Prepared {len(all_updates)} updates for analytics refresh. Expected {15 + 5*len(plot_configs) + num_unique_sections + 6}.") | |
# The status_msg from plot_gen_results is the first item. We can augment it. | |
all_updates[0] = f"{status_msg} | {agentic_status_update}" | |
return tuple(all_updates) # Return as tuple for Gradio | |
# Define the list of outputs for apply_filter_btn and sync.then | |
apply_filter_and_sync_outputs_list = [analytics_status_md] | |
apply_filter_and_sync_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("plot_component", gr.update()) for pc in plot_configs]) | |
_ui_resets_for_filter = [ | |
global_actions_column_ui, insights_chatbot_ui, insights_chat_input_ui, | |
insights_suggestions_row_ui, insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn, | |
formula_display_markdown_ui, formula_close_hint_md | |
] | |
apply_filter_and_sync_outputs_list.extend(_ui_resets_for_filter) | |
_state_resets_for_filter = [active_panel_action_state, current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st] | |
apply_filter_and_sync_outputs_list.extend(_state_resets_for_filter) | |
for pc in plot_configs: | |
pid = pc["id"] | |
apply_filter_and_sync_outputs_list.extend([ | |
plot_ui_objects.get(pid, {}).get("bomb_button", gr.update()), | |
plot_ui_objects.get(pid, {}).get("formula_button", gr.update()), | |
plot_ui_objects.get(pid, {}).get("explore_button", gr.update()), | |
plot_ui_objects.get(pid, {}).get("panel_component", gr.update()) | |
]) | |
apply_filter_and_sync_outputs_list.append(explored_plot_id_state) | |
apply_filter_and_sync_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections]) | |
# --- Add new outputs for agentic tabs and states --- | |
apply_filter_and_sync_outputs_list.extend([ | |
agentic_report_display_md, # Tab 5 UI | |
key_results_cbg, # Tab 6 UI (CheckboxGroup) | |
okr_detail_display_md, # Tab 6 UI (Markdown display) | |
orchestration_raw_results_st, # New State | |
selected_key_result_ids_st, # New State | |
key_results_for_selection_st # New State | |
]) | |
# --- End new outputs --- | |
apply_filter_btn.click( | |
fn=refresh_all_analytics_ui_elements, # Now async | |
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st], | |
outputs=apply_filter_and_sync_outputs_list, show_progress="full" | |
) | |
# --- Sync Events (at the end of the app's 'with gr.Blocks()' context) --- | |
sync_event_part1 = sync_data_btn.click(fn=sync_all_linkedin_data_orchestrator, inputs=[token_state], outputs=[sync_status_html_output, token_state], show_progress="full") | |
sync_event_part2 = sync_event_part1.then(fn=process_and_store_bubble_token, inputs=[url_user_token_display, org_urn_display, token_state], outputs=[status_box, token_state, sync_data_btn], show_progress=False) | |
sync_event_part3 = sync_event_part2.then(fn=display_main_dashboard, inputs=[token_state], outputs=[dashboard_display_html], show_progress=False) | |
sync_event_final = sync_event_part3.then( | |
fn=refresh_all_analytics_ui_elements, # Now async | |
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st], | |
outputs=apply_filter_and_sync_outputs_list, | |
show_progress="full" | |
) | |
if __name__ == "__main__": | |
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 AGENTIC_MODULES_LOADED: | |
logging.warning("CRITICAL: Agentic pipeline modules (run_agentic_pipeline.py, insights_ui_generator.py) failed to load. Tabs 5 and 6 will be non-functional.") | |
if not os.environ.get("GEMINI_API_KEY") and AGENTIC_MODULES_LOADED: | |
logging.warning("ATTENZIONE: 'GEMINI_API_KEY' non impostata. La pipeline AI per le tab 5 e 6 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) | |