GuglielmoTor commited on
Commit
092a033
·
verified ·
1 Parent(s): c231c9f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +234 -102
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
- return [message] + placeholder_figs
 
 
 
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, # For demographic follower plots
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
- return [error_msg] + placeholder_figs
 
 
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
- # Titles for error reporting, matching the order in plot_configs
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) - These might use pre-generated figs if logic was complex
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] # Slice to ensure correct number
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
- return [error_msg_display] + plot_figs[:num_expected_plots]
 
 
 
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
- return [error_msg] + placeholder_figs_general
 
 
 
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(plot_id_clicked, clicked_plot_label)
380
- chat_history_for_this_plot = [initial_insight_msg]
 
 
 
 
381
  updated_chat_histories = current_chat_histories.copy()
382
  updated_chat_histories[plot_id_clicked] = chat_history_for_this_plot
383
  else:
384
- _, suggestions = get_initial_insight_and_suggestions(plot_id_clicked, clicked_plot_label)
 
 
 
 
 
 
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
- yield history_for_plot, "", chat_histories
 
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
- yield history_for_plot, "", chat_histories
451
-
452
- bot_response_text = await generate_llm_response(user_message, current_plot_id, plot_label, history_for_plot)
 
 
 
 
 
 
 
 
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
- plot_config = next((p for p in plot_configs if p["id"] == current_plot_id), None)
472
- plot_label = plot_config["label"] if plot_config else "Grafico Selezionato"
473
-
474
- history_for_plot = chat_histories.get(current_plot_id, []).copy()
475
- history_for_plot.append({"role": "user", "content": suggestion_text})
476
-
477
- yield history_for_plot, "", chat_histories
 
 
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
- # This inner function is what Gradio will call. It's async.
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 # Return the async handler function itself
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=[insights_chat_input_ui, current_chat_plot_id_st, chat_histories_st],
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, current_chat_plot_id_st, chat_histories_st],
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, current_chat_plot_id_st, chat_histories_st],
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, current_chat_plot_id_st, chat_histories_st],
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.") # Changed from error to warning
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)