Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -6,6 +6,7 @@ import matplotlib
|
|
6 |
matplotlib.use('Agg') # Set backend for Matplotlib to avoid GUI conflicts with Gradio
|
7 |
import matplotlib.pyplot as plt
|
8 |
import time # For profiling if needed
|
|
|
9 |
|
10 |
# --- Module Imports ---
|
11 |
from gradio_utils import get_url_user_token
|
@@ -78,31 +79,131 @@ PLOT_ID_TO_FORMULA_KEY_MAP = {
|
|
78 |
"mention_analysis_sentiment": "mention_sentiment"
|
79 |
}
|
80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
# --- Analytics Tab: Plot Figure Generation Function ---
|
82 |
-
def update_analytics_plots_figures(token_state_value, date_filter_option, custom_start_date, custom_end_date):
|
83 |
logging.info(f"Updating analytics plot figures. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
|
84 |
num_expected_plots = 19 # Ensure this matches the number of plots generated
|
85 |
|
|
|
|
|
86 |
if not token_state_value or not token_state_value.get("token"):
|
87 |
message = "❌ Accesso negato. Nessun token. Impossibile generare le analisi."
|
88 |
logging.warning(message)
|
89 |
placeholder_figs = [create_placeholder_plot(title="Accesso Negato", message="Nessun token.") for _ in range(num_expected_plots)]
|
90 |
-
|
|
|
|
|
|
|
91 |
try:
|
92 |
-
# This call is crucial. The structure of these DataFrames determines if plots work.
|
93 |
(filtered_merged_posts_df,
|
94 |
filtered_mentions_df,
|
95 |
date_filtered_follower_stats_df, # For time-based follower plots
|
96 |
-
raw_follower_stats_df,
|
97 |
start_dt_for_msg, end_dt_for_msg) = \
|
98 |
prepare_filtered_analytics_data(
|
99 |
token_state_value, date_filter_option, custom_start_date, custom_end_date
|
100 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
except Exception as e:
|
102 |
error_msg = f"❌ Errore durante la preparazione dei dati per le analisi: {e}"
|
103 |
logging.error(error_msg, exc_info=True)
|
104 |
placeholder_figs = [create_placeholder_plot(title="Errore Preparazione Dati", message=str(e)) for _ in range(num_expected_plots)]
|
105 |
-
|
|
|
|
|
106 |
|
107 |
date_column_posts = token_state_value.get("config_date_col_posts", "published_at")
|
108 |
date_column_mentions = token_state_value.get("config_date_col_mentions", "date")
|
@@ -111,14 +212,9 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
111 |
|
112 |
plot_figs = [] # Initialize list to hold plot figures
|
113 |
|
114 |
-
|
115 |
-
plot_titles_for_errors = [p_cfg["label"] for p_cfg in plot_configs]
|
116 |
|
117 |
try:
|
118 |
-
# Attempt to generate plots. If DataFrames are not as expected by generator functions,
|
119 |
-
# KeyErrors for missing columns or other ValueErrors might occur.
|
120 |
-
# The order of appends MUST match the order in plot_configs for UI updates.
|
121 |
-
|
122 |
# Dinamiche dei Follower (2 plots)
|
123 |
plot_figs.append(generate_followers_count_over_time_plot(date_filtered_follower_stats_df, type_value='follower_gains_monthly'))
|
124 |
plot_figs.append(generate_followers_growth_rate_plot(date_filtered_follower_stats_df, type_value='follower_gains_monthly'))
|
@@ -139,21 +235,19 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
139 |
plot_figs.append(generate_clicks_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
140 |
plot_figs.append(generate_shares_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
141 |
plot_figs.append(generate_comments_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
142 |
-
plot_figs.append(generate_comments_sentiment_breakdown_plot(filtered_merged_posts_df, sentiment_column='comment_sentiment'))
|
143 |
|
144 |
# Analisi Strategia Contenuti (3 plots)
|
145 |
plot_figs.append(generate_post_frequency_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
146 |
plot_figs.append(generate_content_format_breakdown_plot(filtered_merged_posts_df, format_col=media_type_col_name))
|
147 |
plot_figs.append(generate_content_topic_breakdown_plot(filtered_merged_posts_df, topics_col=eb_labels_col_name))
|
148 |
|
149 |
-
# Analisi Menzioni (Dettaglio) (2 plots)
|
150 |
-
# For simplicity here, assuming direct calls. If they were shared, that logic would be here.
|
151 |
plot_figs.append(generate_mentions_activity_plot(filtered_mentions_df, date_column=date_column_mentions))
|
152 |
-
plot_figs.append(generate_mention_sentiment_plot(filtered_mentions_df))
|
153 |
|
154 |
if len(plot_figs) != num_expected_plots:
|
155 |
logging.warning(f"Mismatch in generated plots. Expected {num_expected_plots}, got {len(plot_figs)}. This will cause UI update issues.")
|
156 |
-
# Fill with placeholders if not enough plots were generated due to an early exit or logic error
|
157 |
while len(plot_figs) < num_expected_plots:
|
158 |
plot_figs.append(create_placeholder_plot(title="Grafico Non Generato", message="Logica di generazione incompleta."))
|
159 |
|
@@ -163,7 +257,6 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
163 |
e_display = end_dt_for_msg.strftime('%Y-%m-%d') if end_dt_for_msg else "Qualsiasi"
|
164 |
message += f" (Da: {s_display} A: {e_display})"
|
165 |
|
166 |
-
# Ensure all plot_figs are actual plot objects, not None or error strings
|
167 |
final_plot_figs = []
|
168 |
for i, p_fig_candidate in enumerate(plot_figs):
|
169 |
if p_fig_candidate is not None and not isinstance(p_fig_candidate, str): # Basic check for a plot object
|
@@ -173,30 +266,30 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
173 |
logging.warning(f"Plot {err_title} (index {i}) non è una figura valida: {p_fig_candidate}. Uso placeholder.")
|
174 |
final_plot_figs.append(create_placeholder_plot(title=f"Errore: {err_title}", message="Impossibile generare figura."))
|
175 |
|
176 |
-
return [message] + final_plot_figs[:num_expected_plots]
|
177 |
|
178 |
except (KeyError, ValueError) as e_plot_data:
|
179 |
-
# This catches errors if a specific plot function fails due to missing columns or bad data
|
180 |
logging.error(f"Errore dati durante la generazione di un grafico specifico: {e_plot_data}", exc_info=True)
|
181 |
-
# We don't know which plot failed, so fill all with placeholders after this point
|
182 |
-
# Or, more robustly, identify which plot failed and insert placeholder there.
|
183 |
-
# For now, this will likely result in fewer than num_expected_plots if it happens mid-way.
|
184 |
-
# The loop below will fill remaining with placeholders.
|
185 |
error_msg_display = f"Errore dati in un grafico: {str(e_plot_data)[:100]}"
|
186 |
|
187 |
-
# Fill remaining plot_figs with placeholders
|
188 |
num_already_generated = len(plot_figs)
|
189 |
for i in range(num_already_generated, num_expected_plots):
|
190 |
err_title_fill = plot_titles_for_errors[i] if i < len(plot_titles_for_errors) else f"Grafico {i+1}"
|
191 |
plot_figs.append(create_placeholder_plot(title=f"Errore Dati: {err_title_fill}", message=f"Precedente errore: {str(e_plot_data)[:50]}"))
|
192 |
|
193 |
-
|
|
|
|
|
|
|
194 |
|
195 |
except Exception as e_general:
|
196 |
error_msg = f"❌ Errore generale durante la generazione dei grafici: {e_general}"
|
197 |
logging.error(error_msg, exc_info=True)
|
198 |
placeholder_figs_general = [create_placeholder_plot(title=plot_titles_for_errors[i] if i < len(plot_titles_for_errors) else f"Grafico {i+1}", message=str(e_general)) for i in range(num_expected_plots)]
|
199 |
-
|
|
|
|
|
|
|
200 |
|
201 |
|
202 |
# --- Gradio UI Blocks ---
|
@@ -214,6 +307,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
214 |
|
215 |
chat_histories_st = gr.State({})
|
216 |
current_chat_plot_id_st = gr.State(None)
|
|
|
217 |
|
218 |
gr.Markdown("# 🚀 LinkedIn Organization Dashboard")
|
219 |
url_user_token_display = gr.Textbox(label="User Token (Nascosto)", interactive=False, visible=False)
|
@@ -252,8 +346,8 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
252 |
label="Seleziona Intervallo Date", value="Sempre", scale=3
|
253 |
)
|
254 |
with gr.Column(scale=2):
|
255 |
-
custom_start_date_picker = gr.DateTime(label="Data Inizio", visible=False, include_time=False, type="datetime")
|
256 |
-
custom_end_date_picker = gr.DateTime(label="Data Fine", visible=False, include_time=False, type="datetime")
|
257 |
|
258 |
apply_filter_btn = gr.Button("🔍 Applica Filtro & Aggiorna Analisi", variant="primary")
|
259 |
|
@@ -267,6 +361,8 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
267 |
outputs=[custom_start_date_picker, custom_end_date_picker]
|
268 |
)
|
269 |
|
|
|
|
|
270 |
plot_configs = [
|
271 |
{"label": "Numero di Follower nel Tempo", "id": "followers_count", "section": "Dinamiche dei Follower"},
|
272 |
{"label": "Tasso di Crescita Follower", "id": "followers_growth_rate", "section": "Dinamiche dei Follower"},
|
@@ -325,15 +421,16 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
325 |
action_type: str,
|
326 |
current_active_action_from_state: dict,
|
327 |
current_chat_histories: dict,
|
328 |
-
current_chat_plot_id: str
|
|
|
329 |
):
|
330 |
logging.info(f"Azione '{action_type}' per grafico: {plot_id_clicked}. Attualmente attivo: {current_active_action_from_state}")
|
331 |
|
332 |
clicked_plot_config = next((p for p in plot_configs if p["id"] == plot_id_clicked), None)
|
333 |
if not clicked_plot_config:
|
334 |
logging.error(f"Configurazione non trovata per plot_id {plot_id_clicked}")
|
335 |
-
num_button_updates = 2 * len(plot_configs)
|
336 |
-
error_updates = [gr.update(visible=False)] * 10
|
337 |
error_updates.extend([current_active_action_from_state, current_chat_plot_id, current_chat_histories])
|
338 |
error_updates.extend([gr.update()] * num_button_updates)
|
339 |
return error_updates
|
@@ -375,13 +472,26 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
375 |
new_current_chat_plot_id = plot_id_clicked
|
376 |
chat_history_for_this_plot = current_chat_histories.get(plot_id_clicked, [])
|
377 |
|
|
|
|
|
|
|
378 |
if not chat_history_for_this_plot:
|
379 |
-
initial_insight_msg, suggestions = get_initial_insight_and_suggestions(
|
380 |
-
|
|
|
|
|
|
|
|
|
381 |
updated_chat_histories = current_chat_histories.copy()
|
382 |
updated_chat_histories[plot_id_clicked] = chat_history_for_this_plot
|
383 |
else:
|
384 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
385 |
|
386 |
chatbot_content_update = gr.update(value=chat_history_for_this_plot)
|
387 |
suggestion_1_update = gr.update(value=suggestions[0])
|
@@ -409,10 +519,12 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
409 |
all_button_icon_updates = []
|
410 |
for cfg_item in plot_configs:
|
411 |
p_id_iter = cfg_item["id"]
|
|
|
412 |
if new_active_action_state_to_set == {"plot_id": p_id_iter, "type": "insights"}:
|
413 |
all_button_icon_updates.append(gr.update(value=ACTIVE_ICON))
|
414 |
else:
|
415 |
all_button_icon_updates.append(gr.update(value=BOMB_ICON))
|
|
|
416 |
if new_active_action_state_to_set == {"plot_id": p_id_iter, "type": "formula"}:
|
417 |
all_button_icon_updates.append(gr.update(value=ACTIVE_ICON))
|
418 |
else:
|
@@ -425,8 +537,8 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
425 |
insights_suggestions_row_visible_update, suggestion_1_update, suggestion_2_update, suggestion_3_update,
|
426 |
formula_display_visible_update, formula_content_update,
|
427 |
new_active_action_state_to_set,
|
428 |
-
new_current_chat_plot_id,
|
429 |
-
updated_chat_histories
|
430 |
] + all_button_icon_updates
|
431 |
|
432 |
return final_updates
|
@@ -435,21 +547,35 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
435 |
user_message: str,
|
436 |
current_plot_id: str,
|
437 |
chat_histories: dict,
|
|
|
438 |
):
|
439 |
if not current_plot_id or not user_message.strip():
|
440 |
history_for_plot = chat_histories.get(current_plot_id, [])
|
441 |
-
|
|
|
442 |
return
|
443 |
|
444 |
plot_config = next((p for p in plot_configs if p["id"] == current_plot_id), None)
|
445 |
plot_label = plot_config["label"] if plot_config else "Grafico Selezionato"
|
|
|
|
|
|
|
|
|
446 |
|
447 |
history_for_plot = chat_histories.get(current_plot_id, []).copy()
|
448 |
history_for_plot.append({"role": "user", "content": user_message})
|
449 |
|
450 |
-
|
451 |
-
|
452 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
453 |
|
454 |
history_for_plot.append({"role": "assistant", "content": bot_response_text})
|
455 |
|
@@ -458,38 +584,35 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
458 |
|
459 |
yield history_for_plot, "", updated_chat_histories
|
460 |
|
|
|
461 |
async def handle_suggested_question_click(
|
462 |
suggestion_text: str,
|
463 |
current_plot_id: str,
|
464 |
chat_histories: dict,
|
|
|
465 |
):
|
466 |
if not current_plot_id or not suggestion_text.strip():
|
467 |
history_for_plot = chat_histories.get(current_plot_id, [])
|
468 |
-
yield history_for_plot, "", chat_histories
|
469 |
return
|
470 |
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
|
|
|
|
478 |
|
479 |
-
bot_response_text = await generate_llm_response(suggestion_text, current_plot_id, plot_label, history_for_plot)
|
480 |
-
history_for_plot.append({"role": "assistant", "content": bot_response_text})
|
481 |
-
|
482 |
-
updated_chat_histories = chat_histories.copy()
|
483 |
-
updated_chat_histories[current_plot_id] = history_for_plot
|
484 |
-
|
485 |
-
yield history_for_plot, "", updated_chat_histories
|
486 |
|
487 |
def handle_explore_click(plot_id_clicked, current_explored_plot_id_from_state):
|
488 |
logging.info(f"Click su Esplora per: {plot_id_clicked}. Attualmente esplorato da stato: {current_explored_plot_id_from_state}")
|
489 |
if not plot_ui_objects:
|
490 |
logging.error("plot_ui_objects non popolato durante handle_explore_click.")
|
491 |
updates_for_missing_ui = [current_explored_plot_id_from_state]
|
492 |
-
for _ in plot_configs:
|
493 |
updates_for_missing_ui.extend([gr.update(), gr.update()])
|
494 |
return updates_for_missing_ui
|
495 |
|
@@ -502,7 +625,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
502 |
else:
|
503 |
new_explored_id_to_set = plot_id_clicked
|
504 |
logging.info(f"Esplorazione grafico: {plot_id_clicked}")
|
505 |
-
|
506 |
panel_and_button_updates = []
|
507 |
for cfg in plot_configs:
|
508 |
p_id = cfg["id"]
|
@@ -516,16 +639,17 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
516 |
panel_and_button_updates.append(gr.update(value=EXPLORE_ICON))
|
517 |
else:
|
518 |
panel_and_button_updates.extend([gr.update(), gr.update()])
|
519 |
-
|
520 |
final_updates = [new_explored_id_to_set] + panel_and_button_updates
|
521 |
return final_updates
|
522 |
|
|
|
523 |
action_panel_outputs_list = [
|
524 |
global_actions_column_ui,
|
525 |
-
insights_chatbot_ui, insights_chatbot_ui,
|
526 |
insights_chat_input_ui,
|
527 |
insights_suggestions_row_ui, insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn,
|
528 |
-
formula_display_markdown_ui, formula_display_markdown_ui,
|
529 |
active_panel_action_state,
|
530 |
current_chat_plot_id_st,
|
531 |
chat_histories_st
|
@@ -538,6 +662,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
538 |
else:
|
539 |
action_panel_outputs_list.extend([None, None])
|
540 |
|
|
|
541 |
explore_buttons_outputs_list = [explored_plot_id_state]
|
542 |
for cfg_item_explore in plot_configs:
|
543 |
pid_explore = cfg_item_explore["id"]
|
@@ -547,30 +672,29 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
547 |
else:
|
548 |
explore_buttons_outputs_list.extend([None, None])
|
549 |
|
|
|
550 |
action_click_inputs = [
|
551 |
active_panel_action_state,
|
552 |
chat_histories_st,
|
553 |
-
current_chat_plot_id_st
|
|
|
554 |
]
|
|
|
555 |
explore_click_inputs = [explored_plot_id_state]
|
556 |
|
557 |
-
# --- Define "creator" functions for async click handlers ---
|
558 |
def create_panel_action_handler(p_id, action_type_str):
|
559 |
-
|
560 |
-
async def _handler(current_active_val, current_chats_val, current_chat_pid):
|
561 |
logging.debug(f"Entering _handler for plot_id: {p_id}, action: {action_type_str}")
|
562 |
-
result = await handle_panel_action(p_id, action_type_str, current_active_val, current_chats_val, current_chat_pid)
|
563 |
logging.debug(f"_handler for plot_id: {p_id}, action: {action_type_str} completed.")
|
564 |
return result
|
565 |
-
return _handler
|
566 |
-
# --- End "creator" functions ---
|
567 |
|
568 |
for config_item in plot_configs:
|
569 |
plot_id = config_item["id"]
|
570 |
if plot_id in plot_ui_objects:
|
571 |
ui_obj = plot_ui_objects[plot_id]
|
572 |
|
573 |
-
# The 'fn' passed to .click() is the async function returned by the creator
|
574 |
ui_obj["bomb_button"].click(
|
575 |
fn=create_panel_action_handler(plot_id, "insights"),
|
576 |
inputs=action_click_inputs,
|
@@ -593,39 +717,45 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
593 |
logging.warning(f"Oggetto UI per plot_id '{plot_id}' non trovato durante il tentativo di associare i gestori di click.")
|
594 |
|
595 |
chat_submission_outputs = [insights_chatbot_ui, insights_chat_input_ui, chat_histories_st]
|
|
|
|
|
596 |
insights_chat_input_ui.submit(
|
597 |
fn=handle_chat_message_submission,
|
598 |
-
inputs=
|
599 |
outputs=chat_submission_outputs,
|
600 |
api_name="submit_chat_message"
|
601 |
)
|
602 |
|
|
|
603 |
insights_suggestion_1_btn.click(
|
604 |
fn=handle_suggested_question_click,
|
605 |
-
inputs=[insights_suggestion_1_btn
|
606 |
outputs=chat_submission_outputs,
|
607 |
api_name="click_suggestion_1"
|
608 |
)
|
609 |
insights_suggestion_2_btn.click(
|
610 |
fn=handle_suggested_question_click,
|
611 |
-
inputs=[insights_suggestion_2_btn
|
612 |
outputs=chat_submission_outputs,
|
613 |
api_name="click_suggestion_2"
|
614 |
)
|
615 |
insights_suggestion_3_btn.click(
|
616 |
fn=handle_suggested_question_click,
|
617 |
-
inputs=[insights_suggestion_3_btn
|
618 |
outputs=chat_submission_outputs,
|
619 |
api_name="click_suggestion_3"
|
620 |
)
|
621 |
|
622 |
def refresh_all_analytics_ui_elements(current_token_state, date_filter_val, custom_start_val, custom_end_val, current_chat_histories):
|
623 |
logging.info("Aggiornamento di tutti gli elementi UI delle analisi e reset delle azioni/chat.")
|
|
|
|
|
624 |
plot_generation_results = update_analytics_plots_figures(
|
625 |
-
current_token_state, date_filter_val, custom_start_val, custom_end_val
|
626 |
)
|
627 |
status_message_update = plot_generation_results[0]
|
628 |
-
generated_plot_figures = plot_generation_results[1
|
|
|
629 |
|
630 |
all_updates = [status_message_update]
|
631 |
|
@@ -636,30 +766,31 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
636 |
all_updates.append(create_placeholder_plot("Errore Figura", f"Figura mancante per grafico {plot_configs[i]['id']}"))
|
637 |
|
638 |
all_updates.extend([
|
639 |
-
gr.update(visible=False),
|
640 |
-
gr.update(value=[], visible=False),
|
641 |
-
gr.update(value="", visible=False),
|
642 |
-
gr.update(visible=False),
|
643 |
-
gr.update(value="Suggerimento 1", visible=True),
|
644 |
-
gr.update(value="Suggerimento 2", visible=True),
|
645 |
-
gr.update(value="Suggerimento 3", visible=True),
|
646 |
-
gr.update(value="I dettagli sulla formula/metodologia appariranno qui.", visible=False),
|
647 |
-
None,
|
648 |
-
None,
|
649 |
-
current_chat_histories,
|
|
|
650 |
])
|
651 |
|
652 |
for cfg in plot_configs:
|
653 |
pid = cfg["id"]
|
654 |
if pid in plot_ui_objects:
|
655 |
-
all_updates.append(gr.update(value=BOMB_ICON))
|
656 |
all_updates.append(gr.update(value=FORMULA_ICON))
|
657 |
all_updates.append(gr.update(value=EXPLORE_ICON))
|
658 |
-
all_updates.append(gr.update(visible=True))
|
659 |
else:
|
660 |
all_updates.extend([None, None, None, None])
|
661 |
|
662 |
-
all_updates.append(None)
|
663 |
|
664 |
logging.info(f"Preparati {len(all_updates)} aggiornamenti per il refresh delle analisi.")
|
665 |
return all_updates
|
@@ -673,17 +804,18 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
673 |
apply_filter_and_sync_outputs_list.append(None)
|
674 |
|
675 |
apply_filter_and_sync_outputs_list.extend([
|
676 |
-
global_actions_column_ui,
|
677 |
-
insights_chatbot_ui,
|
678 |
-
insights_chat_input_ui,
|
679 |
-
insights_suggestions_row_ui,
|
680 |
-
insights_suggestion_1_btn,
|
681 |
-
insights_suggestion_2_btn,
|
682 |
-
insights_suggestion_3_btn,
|
683 |
-
formula_display_markdown_ui,
|
684 |
-
active_panel_action_state,
|
685 |
-
current_chat_plot_id_st,
|
686 |
-
chat_histories_st
|
|
|
687 |
])
|
688 |
|
689 |
for cfg_filter_sync_btns in plot_configs:
|
@@ -696,7 +828,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
696 |
else:
|
697 |
apply_filter_and_sync_outputs_list.extend([None, None, None, None])
|
698 |
|
699 |
-
apply_filter_and_sync_outputs_list.append(explored_plot_id_state)
|
700 |
|
701 |
logging.info(f"Output totali definiti per apply_filter/sync: {len(apply_filter_and_sync_outputs_list)}")
|
702 |
|
@@ -746,7 +878,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
746 |
inputs=[token_state], outputs=[dashboard_display_html], show_progress=False
|
747 |
)
|
748 |
sync_event_final = sync_event_part3.then(
|
749 |
-
fn=refresh_all_analytics_ui_elements,
|
750 |
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st],
|
751 |
outputs=apply_filter_and_sync_outputs_list,
|
752 |
show_progress="full"
|
@@ -763,6 +895,6 @@ if __name__ == "__main__":
|
|
763 |
try:
|
764 |
logging.info(f"Versione Matplotlib: {matplotlib.__version__}, Backend: {matplotlib.get_backend()}")
|
765 |
except ImportError:
|
766 |
-
logging.warning("Matplotlib non trovato direttamente, ma potrebbe essere usato dai generatori di grafici.")
|
767 |
|
768 |
app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
|
|
6 |
matplotlib.use('Agg') # Set backend for Matplotlib to avoid GUI conflicts with Gradio
|
7 |
import matplotlib.pyplot as plt
|
8 |
import time # For profiling if needed
|
9 |
+
from datetime import datetime
|
10 |
|
11 |
# --- Module Imports ---
|
12 |
from gradio_utils import get_url_user_token
|
|
|
79 |
"mention_analysis_sentiment": "mention_sentiment"
|
80 |
}
|
81 |
|
82 |
+
# --- Helper function to generate textual data summaries for chatbot ---
|
83 |
+
def generate_chatbot_data_summaries(
|
84 |
+
plot_configs_list,
|
85 |
+
filtered_merged_posts_df,
|
86 |
+
filtered_mentions_df,
|
87 |
+
date_filtered_follower_stats_df,
|
88 |
+
raw_follower_stats_df,
|
89 |
+
token_state_value # To get column names if needed
|
90 |
+
):
|
91 |
+
"""
|
92 |
+
Generates textual summaries for each plot ID to be used by the chatbot.
|
93 |
+
"""
|
94 |
+
data_summaries = {}
|
95 |
+
date_col_posts = token_state_value.get("config_date_col_posts", "published_at")
|
96 |
+
# Ensure date columns are datetime objects for proper formatting and comparison
|
97 |
+
if date_col_posts in filtered_merged_posts_df.columns:
|
98 |
+
filtered_merged_posts_df[date_col_posts] = pd.to_datetime(filtered_merged_posts_df[date_col_posts], errors='coerce')
|
99 |
+
if 'date' in date_filtered_follower_stats_df.columns:
|
100 |
+
date_filtered_follower_stats_df['date'] = pd.to_datetime(date_filtered_follower_stats_df['date'], errors='coerce')
|
101 |
+
# Add more date conversions as needed for other dataframes
|
102 |
+
|
103 |
+
for plot_cfg in plot_configs_list:
|
104 |
+
plot_id = plot_cfg["id"]
|
105 |
+
summary_text = f"No data summary available for {plot_cfg['label']} for the selected period."
|
106 |
+
|
107 |
+
try:
|
108 |
+
if plot_id == "followers_count" and not date_filtered_follower_stats_df.empty:
|
109 |
+
df_summary = date_filtered_follower_stats_df[['date', 'follower_gains_monthly']].copy()
|
110 |
+
df_summary['date'] = df_summary['date'].dt.strftime('%Y-%m-%d')
|
111 |
+
summary_text = f"Follower Count (Monthly Gains):\n{df_summary.tail(5).to_string(index=False)}"
|
112 |
+
|
113 |
+
elif plot_id == "followers_growth_rate" and not date_filtered_follower_stats_df.empty:
|
114 |
+
df_summary = date_filtered_follower_stats_df[['date', 'growth_rate_monthly']].copy() # Assuming 'growth_rate_monthly' column
|
115 |
+
df_summary['date'] = df_summary['date'].dt.strftime('%Y-%m-%d')
|
116 |
+
summary_text = f"Follower Growth Rate (Monthly):\n{df_summary.tail(5).to_string(index=False)}"
|
117 |
+
|
118 |
+
elif plot_id == "followers_by_location" and not raw_follower_stats_df.empty and 'follower_geo' in raw_follower_stats_df.columns:
|
119 |
+
# Assuming 'follower_geo' contains location names and another column like 'count' exists
|
120 |
+
# This part needs to align with how generate_followers_by_demographics_plot processes 'follower_geo' data
|
121 |
+
# For simplicity, let's assume raw_follower_stats_df has pre-aggregated data for geo
|
122 |
+
# This is a placeholder, actual column names for count and name are needed from your data prep
|
123 |
+
if 'geo_name' in raw_follower_stats_df.columns and 'geo_count' in raw_follower_stats_df.columns:
|
124 |
+
df_summary = raw_follower_stats_df[['geo_name', 'geo_count']].nlargest(5, 'geo_count')
|
125 |
+
summary_text = f"Followers by Location (Top 5):\n{df_summary.to_string(index=False)}"
|
126 |
+
else:
|
127 |
+
summary_text = "Follower location data structure not as expected for summary."
|
128 |
+
|
129 |
+
|
130 |
+
elif plot_id == "engagement_rate" and not filtered_merged_posts_df.empty:
|
131 |
+
df_summary = filtered_merged_posts_df[[date_col_posts, 'engagement_rate']].copy()
|
132 |
+
df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
|
133 |
+
summary_text = f"Engagement Rate Over Time:\n{df_summary.tail(5).to_string(index=False)}"
|
134 |
+
|
135 |
+
elif plot_id == "reach_over_time" and not filtered_merged_posts_df.empty:
|
136 |
+
df_summary = filtered_merged_posts_df[[date_col_posts, 'reach']].copy()
|
137 |
+
df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
|
138 |
+
summary_text = f"Reach Over Time:\n{df_summary.tail(5).to_string(index=False)}"
|
139 |
+
|
140 |
+
elif plot_id == "impressions_over_time" and not filtered_merged_posts_df.empty:
|
141 |
+
df_summary = filtered_merged_posts_df[[date_col_posts, 'impressions_sum']].copy() # Assuming 'impressions_sum' or similar
|
142 |
+
df_summary[date_col_posts] = df_summary[date_col_posts].dt.strftime('%Y-%m-%d')
|
143 |
+
summary_text = f"Impressions Over Time:\n{df_summary.tail(5).to_string(index=False)}"
|
144 |
+
|
145 |
+
elif plot_id == "comments_sentiment" and not filtered_merged_posts_df.empty and 'comment_sentiment' in filtered_merged_posts_df.columns:
|
146 |
+
sentiment_counts = filtered_merged_posts_df['comment_sentiment'].value_counts().reset_index()
|
147 |
+
sentiment_counts.columns = ['Sentiment', 'Count']
|
148 |
+
summary_text = f"Comments Sentiment Breakdown:\n{sentiment_counts.to_string(index=False)}"
|
149 |
+
|
150 |
+
# Add more elif blocks for other plot_ids, extracting relevant data:
|
151 |
+
# e.g., likes_over_time, clicks_over_time, shares_over_time, comments_over_time
|
152 |
+
# post_frequency_cs, content_format_breakdown_cs, content_topic_breakdown_cs
|
153 |
+
# mentions_activity, mention_sentiment
|
154 |
+
|
155 |
+
data_summaries[plot_id] = summary_text
|
156 |
+
except KeyError as e:
|
157 |
+
logging.warning(f"KeyError generating summary for {plot_id}: {e}. Using default summary.")
|
158 |
+
data_summaries[plot_id] = f"Data summary generation error for {plot_cfg['label']} (missing column: {e})."
|
159 |
+
except Exception as e:
|
160 |
+
logging.error(f"Error generating summary for {plot_id}: {e}", exc_info=True)
|
161 |
+
data_summaries[plot_id] = f"Error generating data summary for {plot_cfg['label']}."
|
162 |
+
|
163 |
+
return data_summaries
|
164 |
+
|
165 |
# --- Analytics Tab: Plot Figure Generation Function ---
|
166 |
+
def update_analytics_plots_figures(token_state_value, date_filter_option, custom_start_date, custom_end_date, current_plot_configs):
|
167 |
logging.info(f"Updating analytics plot figures. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
|
168 |
num_expected_plots = 19 # Ensure this matches the number of plots generated
|
169 |
|
170 |
+
plot_data_summaries_for_chatbot = {} # Initialize dict for chatbot summaries
|
171 |
+
|
172 |
if not token_state_value or not token_state_value.get("token"):
|
173 |
message = "❌ Accesso negato. Nessun token. Impossibile generare le analisi."
|
174 |
logging.warning(message)
|
175 |
placeholder_figs = [create_placeholder_plot(title="Accesso Negato", message="Nessun token.") for _ in range(num_expected_plots)]
|
176 |
+
# For each plot_config, add a default "no data" summary
|
177 |
+
for p_cfg in current_plot_configs:
|
178 |
+
plot_data_summaries_for_chatbot[p_cfg["id"]] = "Accesso negato, nessun dato per il chatbot."
|
179 |
+
return [message] + placeholder_figs + [plot_data_summaries_for_chatbot]
|
180 |
try:
|
|
|
181 |
(filtered_merged_posts_df,
|
182 |
filtered_mentions_df,
|
183 |
date_filtered_follower_stats_df, # For time-based follower plots
|
184 |
+
raw_follower_stats_df, # For demographic follower plots
|
185 |
start_dt_for_msg, end_dt_for_msg) = \
|
186 |
prepare_filtered_analytics_data(
|
187 |
token_state_value, date_filter_option, custom_start_date, custom_end_date
|
188 |
)
|
189 |
+
|
190 |
+
# Generate data summaries for chatbot AFTER data preparation
|
191 |
+
plot_data_summaries_for_chatbot = generate_chatbot_data_summaries(
|
192 |
+
current_plot_configs, # Pass the plot_configs list
|
193 |
+
filtered_merged_posts_df,
|
194 |
+
filtered_mentions_df,
|
195 |
+
date_filtered_follower_stats_df,
|
196 |
+
raw_follower_stats_df,
|
197 |
+
token_state_value
|
198 |
+
)
|
199 |
+
|
200 |
except Exception as e:
|
201 |
error_msg = f"❌ Errore durante la preparazione dei dati per le analisi: {e}"
|
202 |
logging.error(error_msg, exc_info=True)
|
203 |
placeholder_figs = [create_placeholder_plot(title="Errore Preparazione Dati", message=str(e)) for _ in range(num_expected_plots)]
|
204 |
+
for p_cfg in current_plot_configs:
|
205 |
+
plot_data_summaries_for_chatbot[p_cfg["id"]] = f"Errore preparazione dati: {e}"
|
206 |
+
return [error_msg] + placeholder_figs + [plot_data_summaries_for_chatbot]
|
207 |
|
208 |
date_column_posts = token_state_value.get("config_date_col_posts", "published_at")
|
209 |
date_column_mentions = token_state_value.get("config_date_col_mentions", "date")
|
|
|
212 |
|
213 |
plot_figs = [] # Initialize list to hold plot figures
|
214 |
|
215 |
+
plot_titles_for_errors = [p_cfg["label"] for p_cfg in current_plot_configs]
|
|
|
216 |
|
217 |
try:
|
|
|
|
|
|
|
|
|
218 |
# Dinamiche dei Follower (2 plots)
|
219 |
plot_figs.append(generate_followers_count_over_time_plot(date_filtered_follower_stats_df, type_value='follower_gains_monthly'))
|
220 |
plot_figs.append(generate_followers_growth_rate_plot(date_filtered_follower_stats_df, type_value='follower_gains_monthly'))
|
|
|
235 |
plot_figs.append(generate_clicks_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
236 |
plot_figs.append(generate_shares_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
237 |
plot_figs.append(generate_comments_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
238 |
+
plot_figs.append(generate_comments_sentiment_breakdown_plot(filtered_merged_posts_df, sentiment_column='comment_sentiment')) # Make sure 'comment_sentiment' exists
|
239 |
|
240 |
# Analisi Strategia Contenuti (3 plots)
|
241 |
plot_figs.append(generate_post_frequency_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
242 |
plot_figs.append(generate_content_format_breakdown_plot(filtered_merged_posts_df, format_col=media_type_col_name))
|
243 |
plot_figs.append(generate_content_topic_breakdown_plot(filtered_merged_posts_df, topics_col=eb_labels_col_name))
|
244 |
|
245 |
+
# Analisi Menzioni (Dettaglio) (2 plots)
|
|
|
246 |
plot_figs.append(generate_mentions_activity_plot(filtered_mentions_df, date_column=date_column_mentions))
|
247 |
+
plot_figs.append(generate_mention_sentiment_plot(filtered_mentions_df)) # Make sure this function handles empty/malformed df
|
248 |
|
249 |
if len(plot_figs) != num_expected_plots:
|
250 |
logging.warning(f"Mismatch in generated plots. Expected {num_expected_plots}, got {len(plot_figs)}. This will cause UI update issues.")
|
|
|
251 |
while len(plot_figs) < num_expected_plots:
|
252 |
plot_figs.append(create_placeholder_plot(title="Grafico Non Generato", message="Logica di generazione incompleta."))
|
253 |
|
|
|
257 |
e_display = end_dt_for_msg.strftime('%Y-%m-%d') if end_dt_for_msg else "Qualsiasi"
|
258 |
message += f" (Da: {s_display} A: {e_display})"
|
259 |
|
|
|
260 |
final_plot_figs = []
|
261 |
for i, p_fig_candidate in enumerate(plot_figs):
|
262 |
if p_fig_candidate is not None and not isinstance(p_fig_candidate, str): # Basic check for a plot object
|
|
|
266 |
logging.warning(f"Plot {err_title} (index {i}) non è una figura valida: {p_fig_candidate}. Uso placeholder.")
|
267 |
final_plot_figs.append(create_placeholder_plot(title=f"Errore: {err_title}", message="Impossibile generare figura."))
|
268 |
|
269 |
+
return [message] + final_plot_figs[:num_expected_plots] + [plot_data_summaries_for_chatbot]
|
270 |
|
271 |
except (KeyError, ValueError) as e_plot_data:
|
|
|
272 |
logging.error(f"Errore dati durante la generazione di un grafico specifico: {e_plot_data}", exc_info=True)
|
|
|
|
|
|
|
|
|
273 |
error_msg_display = f"Errore dati in un grafico: {str(e_plot_data)[:100]}"
|
274 |
|
|
|
275 |
num_already_generated = len(plot_figs)
|
276 |
for i in range(num_already_generated, num_expected_plots):
|
277 |
err_title_fill = plot_titles_for_errors[i] if i < len(plot_titles_for_errors) else f"Grafico {i+1}"
|
278 |
plot_figs.append(create_placeholder_plot(title=f"Errore Dati: {err_title_fill}", message=f"Precedente errore: {str(e_plot_data)[:50]}"))
|
279 |
|
280 |
+
for p_cfg in current_plot_configs: # Ensure summaries dict is populated on error
|
281 |
+
if p_cfg["id"] not in plot_data_summaries_for_chatbot:
|
282 |
+
plot_data_summaries_for_chatbot[p_cfg["id"]] = f"Errore dati grafico: {e_plot_data}"
|
283 |
+
return [error_msg_display] + plot_figs[:num_expected_plots] + [plot_data_summaries_for_chatbot]
|
284 |
|
285 |
except Exception as e_general:
|
286 |
error_msg = f"❌ Errore generale durante la generazione dei grafici: {e_general}"
|
287 |
logging.error(error_msg, exc_info=True)
|
288 |
placeholder_figs_general = [create_placeholder_plot(title=plot_titles_for_errors[i] if i < len(plot_titles_for_errors) else f"Grafico {i+1}", message=str(e_general)) for i in range(num_expected_plots)]
|
289 |
+
for p_cfg in current_plot_configs: # Ensure summaries dict is populated on error
|
290 |
+
if p_cfg["id"] not in plot_data_summaries_for_chatbot:
|
291 |
+
plot_data_summaries_for_chatbot[p_cfg["id"]] = f"Errore generale grafici: {e_general}"
|
292 |
+
return [error_msg] + placeholder_figs_general + [plot_data_summaries_for_chatbot]
|
293 |
|
294 |
|
295 |
# --- Gradio UI Blocks ---
|
|
|
307 |
|
308 |
chat_histories_st = gr.State({})
|
309 |
current_chat_plot_id_st = gr.State(None)
|
310 |
+
plot_data_for_chatbot_st = gr.State({}) # NEW: Store data summaries for chatbot
|
311 |
|
312 |
gr.Markdown("# 🚀 LinkedIn Organization Dashboard")
|
313 |
url_user_token_display = gr.Textbox(label="User Token (Nascosto)", interactive=False, visible=False)
|
|
|
346 |
label="Seleziona Intervallo Date", value="Sempre", scale=3
|
347 |
)
|
348 |
with gr.Column(scale=2):
|
349 |
+
custom_start_date_picker = gr.DateTime(label="Data Inizio", visible=False, include_time=False, type="datetime") # Use gr.DateTime
|
350 |
+
custom_end_date_picker = gr.DateTime(label="Data Fine", visible=False, include_time=False, type="datetime") # Use gr.DateTime
|
351 |
|
352 |
apply_filter_btn = gr.Button("🔍 Applica Filtro & Aggiorna Analisi", variant="primary")
|
353 |
|
|
|
361 |
outputs=[custom_start_date_picker, custom_end_date_picker]
|
362 |
)
|
363 |
|
364 |
+
# Moved plot_configs to be globally accessible within the Blocks scope if needed by update_analytics_plots_figures
|
365 |
+
# or pass it explicitly. For now, it's defined here.
|
366 |
plot_configs = [
|
367 |
{"label": "Numero di Follower nel Tempo", "id": "followers_count", "section": "Dinamiche dei Follower"},
|
368 |
{"label": "Tasso di Crescita Follower", "id": "followers_growth_rate", "section": "Dinamiche dei Follower"},
|
|
|
421 |
action_type: str,
|
422 |
current_active_action_from_state: dict,
|
423 |
current_chat_histories: dict,
|
424 |
+
current_chat_plot_id: str,
|
425 |
+
current_plot_data_for_chatbot: dict # NEW: data summaries
|
426 |
):
|
427 |
logging.info(f"Azione '{action_type}' per grafico: {plot_id_clicked}. Attualmente attivo: {current_active_action_from_state}")
|
428 |
|
429 |
clicked_plot_config = next((p for p in plot_configs if p["id"] == plot_id_clicked), None)
|
430 |
if not clicked_plot_config:
|
431 |
logging.error(f"Configurazione non trovata per plot_id {plot_id_clicked}")
|
432 |
+
num_button_updates = 2 * len(plot_configs) # insights, formula buttons
|
433 |
+
error_updates = [gr.update(visible=False)] * 10 # action_col, chatbot, input, suggestions_row, 3x sugg_btn, formula_md
|
434 |
error_updates.extend([current_active_action_from_state, current_chat_plot_id, current_chat_histories])
|
435 |
error_updates.extend([gr.update()] * num_button_updates)
|
436 |
return error_updates
|
|
|
472 |
new_current_chat_plot_id = plot_id_clicked
|
473 |
chat_history_for_this_plot = current_chat_histories.get(plot_id_clicked, [])
|
474 |
|
475 |
+
# Get data summary for this plot
|
476 |
+
plot_specific_data_summary = current_plot_data_for_chatbot.get(plot_id_clicked, f"Nessun sommario dati specifico disponibile per '{clicked_plot_label}'.")
|
477 |
+
|
478 |
if not chat_history_for_this_plot:
|
479 |
+
initial_insight_msg, suggestions = get_initial_insight_and_suggestions(
|
480 |
+
plot_id_clicked,
|
481 |
+
clicked_plot_label,
|
482 |
+
plot_specific_data_summary # Pass the summary
|
483 |
+
)
|
484 |
+
chat_history_for_this_plot = [initial_insight_msg] # Gradio expects list of dicts
|
485 |
updated_chat_histories = current_chat_histories.copy()
|
486 |
updated_chat_histories[plot_id_clicked] = chat_history_for_this_plot
|
487 |
else:
|
488 |
+
# If history exists, still get fresh suggestions, but don't overwrite history's first message
|
489 |
+
_, suggestions = get_initial_insight_and_suggestions(
|
490 |
+
plot_id_clicked,
|
491 |
+
clicked_plot_label,
|
492 |
+
plot_specific_data_summary # Pass summary for context if needed by suggestions
|
493 |
+
)
|
494 |
+
|
495 |
|
496 |
chatbot_content_update = gr.update(value=chat_history_for_this_plot)
|
497 |
suggestion_1_update = gr.update(value=suggestions[0])
|
|
|
519 |
all_button_icon_updates = []
|
520 |
for cfg_item in plot_configs:
|
521 |
p_id_iter = cfg_item["id"]
|
522 |
+
# Update insights button icon
|
523 |
if new_active_action_state_to_set == {"plot_id": p_id_iter, "type": "insights"}:
|
524 |
all_button_icon_updates.append(gr.update(value=ACTIVE_ICON))
|
525 |
else:
|
526 |
all_button_icon_updates.append(gr.update(value=BOMB_ICON))
|
527 |
+
# Update formula button icon
|
528 |
if new_active_action_state_to_set == {"plot_id": p_id_iter, "type": "formula"}:
|
529 |
all_button_icon_updates.append(gr.update(value=ACTIVE_ICON))
|
530 |
else:
|
|
|
537 |
insights_suggestions_row_visible_update, suggestion_1_update, suggestion_2_update, suggestion_3_update,
|
538 |
formula_display_visible_update, formula_content_update,
|
539 |
new_active_action_state_to_set,
|
540 |
+
new_current_chat_plot_id,
|
541 |
+
updated_chat_histories
|
542 |
] + all_button_icon_updates
|
543 |
|
544 |
return final_updates
|
|
|
547 |
user_message: str,
|
548 |
current_plot_id: str,
|
549 |
chat_histories: dict,
|
550 |
+
current_plot_data_for_chatbot: dict # NEW: data summaries
|
551 |
):
|
552 |
if not current_plot_id or not user_message.strip():
|
553 |
history_for_plot = chat_histories.get(current_plot_id, [])
|
554 |
+
# Yield current state if no action needed
|
555 |
+
yield history_for_plot, gr.update(value=""), chat_histories # Clear input, return current history
|
556 |
return
|
557 |
|
558 |
plot_config = next((p for p in plot_configs if p["id"] == current_plot_id), None)
|
559 |
plot_label = plot_config["label"] if plot_config else "Grafico Selezionato"
|
560 |
+
|
561 |
+
# Retrieve the specific data summary for the current plot
|
562 |
+
plot_specific_data_summary = current_plot_data_for_chatbot.get(current_plot_id, f"Nessun sommario dati specifico disponibile per '{plot_label}'.")
|
563 |
+
|
564 |
|
565 |
history_for_plot = chat_histories.get(current_plot_id, []).copy()
|
566 |
history_for_plot.append({"role": "user", "content": user_message})
|
567 |
|
568 |
+
# Update UI immediately with user message
|
569 |
+
yield history_for_plot, gr.update(value=""), chat_histories # Clear input
|
570 |
+
|
571 |
+
# Pass the data summary to the LLM along with the history
|
572 |
+
bot_response_text = await generate_llm_response(
|
573 |
+
user_message,
|
574 |
+
current_plot_id,
|
575 |
+
plot_label,
|
576 |
+
history_for_plot, # This history now includes the initial insight with summary + user message
|
577 |
+
plot_specific_data_summary # Explicitly pass for this turn if needed by LLM handler logic
|
578 |
+
)
|
579 |
|
580 |
history_for_plot.append({"role": "assistant", "content": bot_response_text})
|
581 |
|
|
|
584 |
|
585 |
yield history_for_plot, "", updated_chat_histories
|
586 |
|
587 |
+
|
588 |
async def handle_suggested_question_click(
|
589 |
suggestion_text: str,
|
590 |
current_plot_id: str,
|
591 |
chat_histories: dict,
|
592 |
+
current_plot_data_for_chatbot: dict # NEW: data summaries
|
593 |
):
|
594 |
if not current_plot_id or not suggestion_text.strip():
|
595 |
history_for_plot = chat_histories.get(current_plot_id, [])
|
596 |
+
yield history_for_plot, gr.update(value=""), chat_histories
|
597 |
return
|
598 |
|
599 |
+
# This is essentially the same as submitting a message, so reuse logic
|
600 |
+
# The suggestion_text becomes the user_message
|
601 |
+
async for update in handle_chat_message_submission(
|
602 |
+
suggestion_text,
|
603 |
+
current_plot_id,
|
604 |
+
chat_histories,
|
605 |
+
current_plot_data_for_chatbot
|
606 |
+
):
|
607 |
+
yield update
|
608 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
609 |
|
610 |
def handle_explore_click(plot_id_clicked, current_explored_plot_id_from_state):
|
611 |
logging.info(f"Click su Esplora per: {plot_id_clicked}. Attualmente esplorato da stato: {current_explored_plot_id_from_state}")
|
612 |
if not plot_ui_objects:
|
613 |
logging.error("plot_ui_objects non popolato durante handle_explore_click.")
|
614 |
updates_for_missing_ui = [current_explored_plot_id_from_state]
|
615 |
+
for _ in plot_configs: # panel_component, explore_button
|
616 |
updates_for_missing_ui.extend([gr.update(), gr.update()])
|
617 |
return updates_for_missing_ui
|
618 |
|
|
|
625 |
else:
|
626 |
new_explored_id_to_set = plot_id_clicked
|
627 |
logging.info(f"Esplorazione grafico: {plot_id_clicked}")
|
628 |
+
|
629 |
panel_and_button_updates = []
|
630 |
for cfg in plot_configs:
|
631 |
p_id = cfg["id"]
|
|
|
639 |
panel_and_button_updates.append(gr.update(value=EXPLORE_ICON))
|
640 |
else:
|
641 |
panel_and_button_updates.extend([gr.update(), gr.update()])
|
642 |
+
|
643 |
final_updates = [new_explored_id_to_set] + panel_and_button_updates
|
644 |
return final_updates
|
645 |
|
646 |
+
# Outputs for panel actions
|
647 |
action_panel_outputs_list = [
|
648 |
global_actions_column_ui,
|
649 |
+
insights_chatbot_ui, insights_chatbot_ui, # Target chatbot UI for visibility and value
|
650 |
insights_chat_input_ui,
|
651 |
insights_suggestions_row_ui, insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn,
|
652 |
+
formula_display_markdown_ui, formula_display_markdown_ui, # Target markdown for visibility and value
|
653 |
active_panel_action_state,
|
654 |
current_chat_plot_id_st,
|
655 |
chat_histories_st
|
|
|
662 |
else:
|
663 |
action_panel_outputs_list.extend([None, None])
|
664 |
|
665 |
+
# Outputs for explore actions
|
666 |
explore_buttons_outputs_list = [explored_plot_id_state]
|
667 |
for cfg_item_explore in plot_configs:
|
668 |
pid_explore = cfg_item_explore["id"]
|
|
|
672 |
else:
|
673 |
explore_buttons_outputs_list.extend([None, None])
|
674 |
|
675 |
+
# Inputs for panel actions
|
676 |
action_click_inputs = [
|
677 |
active_panel_action_state,
|
678 |
chat_histories_st,
|
679 |
+
current_chat_plot_id_st,
|
680 |
+
plot_data_for_chatbot_st # NEW: pass data summaries state
|
681 |
]
|
682 |
+
# Inputs for explore actions
|
683 |
explore_click_inputs = [explored_plot_id_state]
|
684 |
|
|
|
685 |
def create_panel_action_handler(p_id, action_type_str):
|
686 |
+
async def _handler(current_active_val, current_chats_val, current_chat_pid, current_plot_data_summaries): # Add summaries
|
|
|
687 |
logging.debug(f"Entering _handler for plot_id: {p_id}, action: {action_type_str}")
|
688 |
+
result = await handle_panel_action(p_id, action_type_str, current_active_val, current_chats_val, current_chat_pid, current_plot_data_summaries) # Pass summaries
|
689 |
logging.debug(f"_handler for plot_id: {p_id}, action: {action_type_str} completed.")
|
690 |
return result
|
691 |
+
return _handler
|
|
|
692 |
|
693 |
for config_item in plot_configs:
|
694 |
plot_id = config_item["id"]
|
695 |
if plot_id in plot_ui_objects:
|
696 |
ui_obj = plot_ui_objects[plot_id]
|
697 |
|
|
|
698 |
ui_obj["bomb_button"].click(
|
699 |
fn=create_panel_action_handler(plot_id, "insights"),
|
700 |
inputs=action_click_inputs,
|
|
|
717 |
logging.warning(f"Oggetto UI per plot_id '{plot_id}' non trovato durante il tentativo di associare i gestori di click.")
|
718 |
|
719 |
chat_submission_outputs = [insights_chatbot_ui, insights_chat_input_ui, chat_histories_st]
|
720 |
+
chat_submission_inputs = [insights_chat_input_ui, current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st] # Add data summaries state
|
721 |
+
|
722 |
insights_chat_input_ui.submit(
|
723 |
fn=handle_chat_message_submission,
|
724 |
+
inputs=chat_submission_inputs,
|
725 |
outputs=chat_submission_outputs,
|
726 |
api_name="submit_chat_message"
|
727 |
)
|
728 |
|
729 |
+
suggestion_click_inputs = [current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st] # Add data summaries state
|
730 |
insights_suggestion_1_btn.click(
|
731 |
fn=handle_suggested_question_click,
|
732 |
+
inputs=[insights_suggestion_1_btn] + suggestion_click_inputs, # Pass button value as first arg
|
733 |
outputs=chat_submission_outputs,
|
734 |
api_name="click_suggestion_1"
|
735 |
)
|
736 |
insights_suggestion_2_btn.click(
|
737 |
fn=handle_suggested_question_click,
|
738 |
+
inputs=[insights_suggestion_2_btn] + suggestion_click_inputs,
|
739 |
outputs=chat_submission_outputs,
|
740 |
api_name="click_suggestion_2"
|
741 |
)
|
742 |
insights_suggestion_3_btn.click(
|
743 |
fn=handle_suggested_question_click,
|
744 |
+
inputs=[insights_suggestion_3_btn] + suggestion_click_inputs,
|
745 |
outputs=chat_submission_outputs,
|
746 |
api_name="click_suggestion_3"
|
747 |
)
|
748 |
|
749 |
def refresh_all_analytics_ui_elements(current_token_state, date_filter_val, custom_start_val, custom_end_val, current_chat_histories):
|
750 |
logging.info("Aggiornamento di tutti gli elementi UI delle analisi e reset delle azioni/chat.")
|
751 |
+
|
752 |
+
# Pass plot_configs to the update function so it can be used by generate_chatbot_data_summaries
|
753 |
plot_generation_results = update_analytics_plots_figures(
|
754 |
+
current_token_state, date_filter_val, custom_start_val, custom_end_val, plot_configs
|
755 |
)
|
756 |
status_message_update = plot_generation_results[0]
|
757 |
+
generated_plot_figures = plot_generation_results[1:-1] # All items except first (status) and last (summaries)
|
758 |
+
new_plot_data_summaries = plot_generation_results[-1] # Last item is the summaries dict
|
759 |
|
760 |
all_updates = [status_message_update]
|
761 |
|
|
|
766 |
all_updates.append(create_placeholder_plot("Errore Figura", f"Figura mancante per grafico {plot_configs[i]['id']}"))
|
767 |
|
768 |
all_updates.extend([
|
769 |
+
gr.update(visible=False), # global_actions_column_ui
|
770 |
+
gr.update(value=[], visible=False), # insights_chatbot_ui (value)
|
771 |
+
gr.update(value="", visible=False), # insights_chat_input_ui (value)
|
772 |
+
gr.update(visible=False), # insights_suggestions_row_ui
|
773 |
+
gr.update(value="Suggerimento 1", visible=True), # insights_suggestion_1_btn
|
774 |
+
gr.update(value="Suggerimento 2", visible=True), # insights_suggestion_2_btn
|
775 |
+
gr.update(value="Suggerimento 3", visible=True), # insights_suggestion_3_btn
|
776 |
+
gr.update(value="I dettagli sulla formula/metodologia appariranno qui.", visible=False), # formula_display_markdown_ui
|
777 |
+
None, # active_panel_action_state
|
778 |
+
None, # current_chat_plot_id_st
|
779 |
+
current_chat_histories, # chat_histories_st (preserve or reset as needed, here preserving)
|
780 |
+
new_plot_data_summaries # NEW: plot_data_for_chatbot_st
|
781 |
])
|
782 |
|
783 |
for cfg in plot_configs:
|
784 |
pid = cfg["id"]
|
785 |
if pid in plot_ui_objects:
|
786 |
+
all_updates.append(gr.update(value=BOMB_ICON))
|
787 |
all_updates.append(gr.update(value=FORMULA_ICON))
|
788 |
all_updates.append(gr.update(value=EXPLORE_ICON))
|
789 |
+
all_updates.append(gr.update(visible=True)) # panel_component visibility
|
790 |
else:
|
791 |
all_updates.extend([None, None, None, None])
|
792 |
|
793 |
+
all_updates.append(None) # explored_plot_id_state
|
794 |
|
795 |
logging.info(f"Preparati {len(all_updates)} aggiornamenti per il refresh delle analisi.")
|
796 |
return all_updates
|
|
|
804 |
apply_filter_and_sync_outputs_list.append(None)
|
805 |
|
806 |
apply_filter_and_sync_outputs_list.extend([
|
807 |
+
global_actions_column_ui, # Reset visibility
|
808 |
+
insights_chatbot_ui, # Reset content & visibility
|
809 |
+
insights_chat_input_ui, # Reset content & visibility
|
810 |
+
insights_suggestions_row_ui, # Reset visibility
|
811 |
+
insights_suggestion_1_btn, # Reset text & visibility
|
812 |
+
insights_suggestion_2_btn,
|
813 |
+
insights_suggestion_3_btn,
|
814 |
+
formula_display_markdown_ui, # Reset content & visibility
|
815 |
+
active_panel_action_state, # Reset state
|
816 |
+
current_chat_plot_id_st, # Reset state
|
817 |
+
chat_histories_st, # Preserve or reset state
|
818 |
+
plot_data_for_chatbot_st # NEW: Update this state
|
819 |
])
|
820 |
|
821 |
for cfg_filter_sync_btns in plot_configs:
|
|
|
828 |
else:
|
829 |
apply_filter_and_sync_outputs_list.extend([None, None, None, None])
|
830 |
|
831 |
+
apply_filter_and_sync_outputs_list.append(explored_plot_id_state) # Reset state
|
832 |
|
833 |
logging.info(f"Output totali definiti per apply_filter/sync: {len(apply_filter_and_sync_outputs_list)}")
|
834 |
|
|
|
878 |
inputs=[token_state], outputs=[dashboard_display_html], show_progress=False
|
879 |
)
|
880 |
sync_event_final = sync_event_part3.then(
|
881 |
+
fn=refresh_all_analytics_ui_elements, # This will now also update chatbot data summaries
|
882 |
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st],
|
883 |
outputs=apply_filter_and_sync_outputs_list,
|
884 |
show_progress="full"
|
|
|
895 |
try:
|
896 |
logging.info(f"Versione Matplotlib: {matplotlib.__version__}, Backend: {matplotlib.get_backend()}")
|
897 |
except ImportError:
|
898 |
+
logging.warning("Matplotlib non trovato direttamente, ma potrebbe essere usato dai generatori di grafici.")
|
899 |
|
900 |
app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|