GuglielmoTor commited on
Commit
0faf79e
Β·
verified Β·
1 Parent(s): af5f5a9

Update services/analytics_tab_module.py

Browse files
Files changed (1) hide show
  1. services/analytics_tab_module.py +53 -88
services/analytics_tab_module.py CHANGED
@@ -1,15 +1,16 @@
1
- # analytics_tab_module.py
2
  import gradio as gr
3
  import pandas as pd
4
  import logging
5
  import time
6
- from datetime import datetime, date, timedelta
7
  import numpy as np
8
  from collections import OrderedDict, defaultdict
9
  import asyncio
10
  import matplotlib # Keep this if create_placeholder_plot or other plot fns need it
11
  matplotlib.use('Agg') # Keep if necessary for plotting functions
12
  import matplotlib.pyplot as plt # Keep if necessary
 
 
13
 
14
  # It's assumed that PLOT_CONFIGS is specific to this analytics tab.
15
  # If it's used elsewhere, it might need to be passed in or imported from a central config.
@@ -34,6 +35,7 @@ PLOT_CONFIGS = [
34
  {"label": "Volume Menzioni nel Tempo (Dettaglio)", "id": "mention_analysis_volume", "section": "Analisi Menzioni (Dettaglio)"},
35
  {"label": "Ripartizione Menzioni per Sentiment (Dettaglio)", "id": "mention_analysis_sentiment", "section": "Analisi Menzioni (Dettaglio)"}
36
  ]
 
37
  assert len(PLOT_CONFIGS) == 19, "Mancata corrispondenza in PLOT_CONFIGS e grafici attesi."
38
 
39
  UNIQUE_ORDERED_SECTIONS = list(OrderedDict.fromkeys(pc["section"] for pc in PLOT_CONFIGS))
@@ -45,7 +47,6 @@ class AnalyticsTab:
45
  plot_id_to_formula_map, plot_formulas_data, icons,
46
  fn_build_plot_area, fn_update_plot_figures, fn_create_placeholder_plot,
47
  fn_get_initial_insight, fn_generate_llm_response):
48
-
49
  # Shared Gradio states passed from the main app
50
  self.token_state = token_state
51
  self.chat_histories_st = chat_histories_st
@@ -99,37 +100,14 @@ class AnalyticsTab:
99
  is_custom = selection == "Intervallo Personalizzato"
100
  return gr.update(visible=is_custom), gr.update(visible=is_custom)
101
 
102
- # --- NEW HELPER FUNCTION ---
103
- # This function handles the conversion from a date object (from gr.Date)
104
- # to a datetime object, which the plotting function likely expects.
105
- def _handle_date_input(self, date_val):
106
- """Converts various date inputs to a timezone-aware datetime object."""
107
- if date_val is None:
108
- return None
109
- if isinstance(date_val, datetime):
110
- return date_val # Already a datetime object
111
- if isinstance(date_val, date):
112
- # Combine the date with the beginning of the day to create a datetime object
113
- return datetime.combine(date_val, datetime.min.time())
114
- # Add handling for string if necessary, though gr.Date usually returns date/datetime objects
115
- if isinstance(date_val, str):
116
- try:
117
- # Attempt to parse a date string like 'YYYY-MM-DD'
118
- parsed_date = datetime.strptime(date_val, '%Y-%m-%d').date()
119
- return datetime.combine(parsed_date, datetime.min.time())
120
- except ValueError:
121
- logging.warning(f"Could not parse date string: {date_val}")
122
- return None
123
- return None
124
-
125
  async def _handle_panel_action(
126
  self, plot_id_clicked: str, action_type: str, current_active_action_from_state: dict,
127
  current_chat_histories: dict, current_chat_plot_id: str,
128
  current_plot_data_for_chatbot: dict, current_explored_plot_id: str
129
  ):
130
  logging.info(f"Panel Action: '{action_type}' for plot '{plot_id_clicked}'. Active: {current_active_action_from_state}, Explored: {current_explored_plot_id}")
131
- clicked_plot_config = next((p for p in PLOT_CONFIGS if p["id"] == plot_id_clicked), None)
132
 
 
133
  if not clicked_plot_config:
134
  logging.error(f"Config not found for plot_id {plot_id_clicked}")
135
  num_plots = len(PLOT_CONFIGS)
@@ -144,6 +122,7 @@ class AnalyticsTab:
144
 
145
  clicked_plot_label = clicked_plot_config["label"]
146
  clicked_plot_section = clicked_plot_config["section"]
 
147
  hypothetical_new_active_state = {"plot_id": plot_id_clicked, "type": action_type}
148
  is_toggling_off = current_active_action_from_state == hypothetical_new_active_state
149
 
@@ -191,7 +170,6 @@ class AnalyticsTab:
191
 
192
  if action_type == "insights": # Specifically when closing insights chat
193
  new_current_chat_plot_id = None # Clear the chat context
194
-
195
  else: # Toggling ON a panel action
196
  new_active_action_state_to_set = hypothetical_new_active_state
197
  action_col_visible_update = gr.update(visible=True)
@@ -211,16 +189,15 @@ class AnalyticsTab:
211
  is_active_formula = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "formula"}
212
  generated_bomb_btn_updates.append(gr.update(value=self.ACTIVE_ICON if is_active_insights else self.BOMB_ICON))
213
  generated_formula_btn_updates.append(gr.update(value=self.ACTIVE_ICON if is_active_formula else self.FORMULA_ICON))
214
-
215
  if action_type == "insights":
216
  insights_chatbot_visible_update = gr.update(visible=True)
217
  insights_chat_input_visible_update = gr.update(visible=True)
218
  insights_suggestions_row_visible_update = gr.update(visible=True)
219
  new_current_chat_plot_id = plot_id_clicked # Set chat context to this plot
220
-
221
  history = current_chat_histories.get(plot_id_clicked, [])
222
  summary = current_plot_data_for_chatbot.get(plot_id_clicked, f"No summary available for '{clicked_plot_label}'.")
223
-
224
  if not history: # First time opening chat for this plot
225
  prompt, sugg = self.get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
226
  llm_history_for_api = [{"role": "user", "content": prompt}] # API expects list of dicts
@@ -229,7 +206,7 @@ class AnalyticsTab:
229
  updated_chat_histories = {**current_chat_histories, plot_id_clicked: history}
230
  else: # Re-opening chat, just get new suggestions if any
231
  _, sugg = self.get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
232
-
233
  chatbot_content_update = gr.update(value=history)
234
  s1_upd = gr.update(value=sugg[0] if sugg and len(sugg) > 0 else "N/A")
235
  s2_upd = gr.update(value=sugg[1] if sugg and len(sugg) > 1 else "N/A")
@@ -251,26 +228,26 @@ class AnalyticsTab:
251
 
252
  # Order of updates must match self.action_panel_outputs_list
253
  final_updates = [
254
- action_col_visible_update, # global_actions_column_ui
255
- insights_chatbot_visible_update, # insights_chatbot_ui (visibility)
256
- chatbot_content_update, # insights_chatbot_ui (content)
257
  insights_chat_input_visible_update, # insights_chat_input_ui
258
  insights_suggestions_row_visible_update, # insights_suggestions_row_ui
259
- s1_upd, s2_upd, s3_upd, # suggestion buttons
260
- formula_display_visible_update, # formula_display_markdown_ui (visibility)
261
- formula_content_update, # formula_display_markdown_ui (content)
262
- formula_close_hint_visible_update, # formula_close_hint_md
263
- new_active_action_state_to_set, # active_panel_action_state
264
- new_current_chat_plot_id, # current_chat_plot_id_st
265
- updated_chat_histories, # chat_histories_st
266
- new_explored_plot_id_to_set # explored_plot_id_state
267
  ]
268
  final_updates.extend(generated_panel_vis_updates) # Plot panel visibilities
269
  final_updates.extend(generated_bomb_btn_updates) # Bomb button icons
270
  final_updates.extend(generated_formula_btn_updates) # Formula button icons
271
  final_updates.extend(generated_explore_btn_updates) # Explore button icons
272
  final_updates.extend(section_title_vis_updates) # Section title visibilities
273
-
274
  logging.debug(f"handle_panel_action returning {len(final_updates)} updates. Expected {15 + 4*len(PLOT_CONFIGS) + NUM_UNIQUE_SECTIONS}.")
275
  return final_updates
276
 
@@ -286,19 +263,17 @@ class AnalyticsTab:
286
 
287
  history_for_api = chat_histories.get(current_plot_id, []).copy()
288
  if not isinstance(history_for_api, list): history_for_api = []
289
-
290
  history_for_api.append({"role": "user", "content": user_message})
291
-
292
  current_display_history = history_for_api.copy()
293
-
294
  yield current_display_history, gr.update(value=""), chat_histories
295
 
296
  assistant_response = await self.generate_llm_response(user_message, current_plot_id, plot_label, history_for_api, summary_data)
297
- history_for_api.append({"role": "assistant", "content": assistant_response})
298
 
 
299
  updated_chat_histories = {**chat_histories, current_plot_id: history_for_api}
300
  current_display_history.append({"role": "assistant", "content": assistant_response})
301
-
302
  yield current_display_history, "", updated_chat_histories
303
 
304
  async def _handle_suggested_question_click(self, suggestion_text: str, current_plot_id: str, chat_histories: dict, current_plot_data_for_chatbot: dict):
@@ -306,7 +281,7 @@ class AnalyticsTab:
306
  current_history_for_plot = chat_histories.get(current_plot_id, [])
307
  yield current_history_for_plot, gr.update(value=""), chat_histories
308
  return
309
-
310
  async for update_chunk in self._handle_chat_message_submission(suggestion_text, current_plot_id, chat_histories, current_plot_data_for_chatbot):
311
  yield update_chunk
312
 
@@ -324,11 +299,11 @@ class AnalyticsTab:
324
 
325
  new_explored_id_to_set = None
326
  is_toggling_off_explore = (plot_id_clicked == current_explored_plot_id_from_state)
327
-
328
  action_col_upd = gr.update()
329
  new_active_panel_state_upd = current_active_panel_action_state
330
  formula_hint_upd = gr.update(visible=False)
331
-
332
  panel_vis_updates = []
333
  explore_btns_updates = []
334
  bomb_btns_updates = [gr.update()] * num_plots
@@ -349,7 +324,6 @@ class AnalyticsTab:
349
  else:
350
  new_explored_id_to_set = plot_id_clicked
351
  logging.info(f"Exploring {plot_id_clicked}. Hiding other plots/sections.")
352
-
353
  for i, sec_name in enumerate(UNIQUE_ORDERED_SECTIONS):
354
  section_title_vis_updates[i] = gr.update(visible=(sec_name == section_of_clicked_plot))
355
 
@@ -357,7 +331,7 @@ class AnalyticsTab:
357
  is_target_plot = (cfg["id"] == new_explored_id_to_set)
358
  panel_vis_updates.append(gr.update(visible=is_target_plot))
359
  explore_btns_updates.append(gr.update(value=self.ACTIVE_ICON if is_target_plot else self.EXPLORE_ICON))
360
-
361
  if current_active_panel_action_state:
362
  logging.info("Closing active insight/formula panel due to explore click.")
363
  action_col_upd = gr.update(visible=False)
@@ -377,7 +351,7 @@ class AnalyticsTab:
377
  final_explore_updates.extend(bomb_btns_updates)
378
  final_explore_updates.extend(formula_btns_updates)
379
  final_explore_updates.extend(section_title_vis_updates)
380
-
381
  logging.debug(f"handle_explore_click returning {len(final_explore_updates)} updates. Expected {4 + 4*len(PLOT_CONFIGS) + NUM_UNIQUE_SECTIONS}.")
382
  return final_explore_updates
383
 
@@ -385,21 +359,13 @@ class AnalyticsTab:
385
  async def _handler(curr_active_val, curr_chats_val, curr_chat_pid, curr_plot_data, curr_explored_id):
386
  return await self._handle_panel_action(p_id, action_type_str, curr_active_val, curr_chats_val, curr_chat_pid, curr_plot_data, curr_explored_id)
387
  return _handler
388
-
389
- # =========================================================================
390
- # == MODIFIED FUNCTION: _refresh_analytics_graphs_ui
391
- # =========================================================================
392
  async def _refresh_analytics_graphs_ui(self, current_token_state_val, date_filter_val, custom_start_val, custom_end_val, current_chat_histories_val):
393
  logging.info("Refreshing analytics graph UI elements and resetting actions/chat (within module).")
394
  start_time = time.time()
395
-
396
- # --- MODIFICATION: Use the helper function to convert date inputs ---
397
- # This ensures that the `update_analytics_plots_figures` function receives a consistent datetime format.
398
- start_date_dt = self._handle_date_input(custom_start_val)
399
- end_date_dt = self._handle_date_input(custom_end_val)
400
-
401
- plot_gen_results = self.update_analytics_plots_figures(current_token_state_val, date_filter_val, start_date_dt, end_date_dt, PLOT_CONFIGS)
402
 
 
 
403
  try:
404
  expected_count = len(PLOT_CONFIGS) + 2
405
  if len(plot_gen_results) == expected_count:
@@ -413,18 +379,18 @@ class AnalyticsTab:
413
  status_msg = f"Errore: L'aggiornamento dei grafici non Γ¨ riuscito a causa di un formato di dati imprevisto."
414
  gen_figs = [self.create_placeholder_plot("Error", f"Dati non validi {i}") for i in range(len(PLOT_CONFIGS))]
415
  new_summaries_for_chatbot = {}
416
-
417
  all_updates = []
418
  # 1. Status Markdown
419
  all_updates.append(status_msg) # For self.analytics_status_md
420
-
421
  # 2. Plot components
422
  if isinstance(gen_figs, list) and len(gen_figs) == len(PLOT_CONFIGS):
423
  all_updates.extend(gen_figs)
424
  else:
425
  logging.error(f"Mismatch in generated figures ({len(gen_figs) if isinstance(gen_figs, list) else 'N/A'}) and plot_configs ({len(PLOT_CONFIGS)})")
426
  all_updates.extend([self.create_placeholder_plot("Error", f"Figura mancante {i}") for i in range(len(PLOT_CONFIGS))])
427
-
428
  # 3. UI Resets for Action Panel (9 components)
429
  all_updates.extend([
430
  gr.update(visible=False), # global_actions_column_ui
@@ -435,7 +401,7 @@ class AnalyticsTab:
435
  gr.update(value="I dettagli sulla formula/metodologia appariranno qui.", visible=False), # formula_display_markdown_ui
436
  gr.update(visible=False) # formula_close_hint_md
437
  ])
438
-
439
  # 4. State Resets (4 states)
440
  all_updates.extend([
441
  None, # active_panel_action_state (reset)
@@ -443,28 +409,30 @@ class AnalyticsTab:
443
  {}, # chat_histories_st (reset all chats on refresh)
444
  new_summaries_for_chatbot # plot_data_for_chatbot_st (update with new summaries)
445
  ])
446
-
447
  # 5. Plot-specific UI Resets (4 components per plot)
448
  for _ in PLOT_CONFIGS:
449
  all_updates.extend([
450
  gr.update(value=self.BOMB_ICON), # bomb_button
451
  gr.update(value=self.FORMULA_ICON), # formula_button
452
  gr.update(value=self.EXPLORE_ICON), # explore_button
453
- gr.update(visible=True) # panel_component (plot visibility itself)
454
  ])
455
-
456
  # 6. Explored Plot ID State Reset (1 state)
457
  all_updates.append(None) # explored_plot_id_state (reset)
458
-
459
  # 7. Section Title Visibilities
460
  all_updates.extend([gr.update(visible=True)] * NUM_UNIQUE_SECTIONS)
461
-
462
  end_time = time.time()
463
  logging.info(f"Analytics graph refresh (module) took {end_time - start_time:.2f} seconds.")
 
464
  expected_len = 1 + len(PLOT_CONFIGS) + 9 + 4 + (4 * len(PLOT_CONFIGS)) + 1 + NUM_UNIQUE_SECTIONS
465
  logging.info(f"Prepared {len(all_updates)} updates for graph refresh. Expected {expected_len}.")
466
  if len(all_updates) != expected_len:
467
  logging.error(f"Output length mismatch in _refresh_analytics_graphs_ui: got {len(all_updates)}, expected {expected_len}")
 
468
  return tuple(all_updates)
469
 
470
  def _define_callback_outputs(self):
@@ -528,8 +496,8 @@ class AnalyticsTab:
528
  graph_refresh_inputs = [
529
  self.token_state, self.date_filter_selector,
530
  self.custom_start_date_picker, self.custom_end_date_picker,
531
- self.chat_histories_st
532
- ]
533
  self.apply_filter_btn.click(
534
  fn=self._refresh_analytics_graphs_ui,
535
  inputs=graph_refresh_inputs,
@@ -537,7 +505,7 @@ class AnalyticsTab:
537
  show_progress="full",
538
  api_name="refresh_analytics_graphs_module"
539
  )
540
-
541
  # Panel action buttons (bomb, formula, explore)
542
  action_click_inputs = [
543
  self.active_panel_action_state, self.chat_histories_st,
@@ -573,7 +541,7 @@ class AnalyticsTab:
573
  )
574
  else:
575
  logging.warning(f"UI object for plot_id '{plot_id}' not found for click handlers in module.")
576
-
577
  # Chat submission
578
  chat_submission_outputs = [self.insights_chatbot_ui, self.insights_chat_input_ui, self.chat_histories_st]
579
  chat_submission_inputs = [
@@ -612,24 +580,21 @@ class AnalyticsTab:
612
 
613
  def create_tab_ui(self):
614
  with gr.TabItem("πŸ“Š Grafici", id="tab_analytics_module"):
615
-
616
  gr.Markdown("## πŸ“ˆ Analisi Performance LinkedIn")
617
  gr.Markdown("Seleziona un intervallo di date per i grafici. Clicca i pulsanti (πŸ’£ Insights, Ζ’ Formula, 🧭 Esplora) su un grafico per azioni.")
618
  self.analytics_status_md = gr.Markdown("Stato analisi grafici...")
619
-
620
  with gr.Row():
621
  self.date_filter_selector = gr.Radio(
622
  ["Sempre", "Ultimi 7 Giorni", "Ultimi 30 Giorni", "Intervallo Personalizzato"],
623
  label="Seleziona Intervallo Date per Grafici", value="Sempre", scale=3
624
  )
625
  with gr.Column(scale=2):
626
- # Using gr.Date to ensure the calendar popup appears reliably.
627
- # The conversion to a datetime object is handled in the _refresh_analytics_graphs_ui method.
628
- self.custom_start_date_picker = gr.Date(label="Data Inizio", visible=False)
629
- self.custom_end_date_picker = gr.Date(label="Data Fine", visible=False)
630
 
631
  self.apply_filter_btn = gr.Button("πŸ” Applica Filtro & Aggiorna Grafici", variant="primary")
632
-
633
  self.date_filter_selector.change(
634
  fn=self._toggle_custom_date_pickers,
635
  inputs=[self.date_filter_selector],
@@ -668,7 +633,7 @@ class AnalyticsTab:
668
  self.insights_suggestion_1_btn = gr.Button(value="Suggerimento 1", size="sm", min_width=50)
669
  self.insights_suggestion_2_btn = gr.Button(value="Suggerimento 2", size="sm", min_width=50)
670
  self.insights_suggestion_3_btn = gr.Button(value="Suggerimento 3", size="sm", min_width=50)
671
-
672
  self.formula_display_markdown_ui = gr.Markdown(
673
  "I dettagli sulla formula/metodologia appariranno qui.", visible=False
674
  )
@@ -676,6 +641,6 @@ class AnalyticsTab:
676
  "<p style='font-size:0.9em; text-align:center; margin-top:10px;'><em>Click the active Ζ’ button on the plot again to close this panel.</em></p>",
677
  visible=False
678
  )
679
-
680
  self._define_callback_outputs()
681
  self._setup_callbacks()
 
 
1
  import gradio as gr
2
  import pandas as pd
3
  import logging
4
  import time
5
+ from datetime import datetime, timedelta
6
  import numpy as np
7
  from collections import OrderedDict, defaultdict
8
  import asyncio
9
  import matplotlib # Keep this if create_placeholder_plot or other plot fns need it
10
  matplotlib.use('Agg') # Keep if necessary for plotting functions
11
  import matplotlib.pyplot as plt # Keep if necessary
12
+ # ADDED: Import the Calendar component
13
+ from gradio_calendar import Calendar
14
 
15
  # It's assumed that PLOT_CONFIGS is specific to this analytics tab.
16
  # If it's used elsewhere, it might need to be passed in or imported from a central config.
 
35
  {"label": "Volume Menzioni nel Tempo (Dettaglio)", "id": "mention_analysis_volume", "section": "Analisi Menzioni (Dettaglio)"},
36
  {"label": "Ripartizione Menzioni per Sentiment (Dettaglio)", "id": "mention_analysis_sentiment", "section": "Analisi Menzioni (Dettaglio)"}
37
  ]
38
+
39
  assert len(PLOT_CONFIGS) == 19, "Mancata corrispondenza in PLOT_CONFIGS e grafici attesi."
40
 
41
  UNIQUE_ORDERED_SECTIONS = list(OrderedDict.fromkeys(pc["section"] for pc in PLOT_CONFIGS))
 
47
  plot_id_to_formula_map, plot_formulas_data, icons,
48
  fn_build_plot_area, fn_update_plot_figures, fn_create_placeholder_plot,
49
  fn_get_initial_insight, fn_generate_llm_response):
 
50
  # Shared Gradio states passed from the main app
51
  self.token_state = token_state
52
  self.chat_histories_st = chat_histories_st
 
100
  is_custom = selection == "Intervallo Personalizzato"
101
  return gr.update(visible=is_custom), gr.update(visible=is_custom)
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  async def _handle_panel_action(
104
  self, plot_id_clicked: str, action_type: str, current_active_action_from_state: dict,
105
  current_chat_histories: dict, current_chat_plot_id: str,
106
  current_plot_data_for_chatbot: dict, current_explored_plot_id: str
107
  ):
108
  logging.info(f"Panel Action: '{action_type}' for plot '{plot_id_clicked}'. Active: {current_active_action_from_state}, Explored: {current_explored_plot_id}")
 
109
 
110
+ clicked_plot_config = next((p for p in PLOT_CONFIGS if p["id"] == plot_id_clicked), None)
111
  if not clicked_plot_config:
112
  logging.error(f"Config not found for plot_id {plot_id_clicked}")
113
  num_plots = len(PLOT_CONFIGS)
 
122
 
123
  clicked_plot_label = clicked_plot_config["label"]
124
  clicked_plot_section = clicked_plot_config["section"]
125
+
126
  hypothetical_new_active_state = {"plot_id": plot_id_clicked, "type": action_type}
127
  is_toggling_off = current_active_action_from_state == hypothetical_new_active_state
128
 
 
170
 
171
  if action_type == "insights": # Specifically when closing insights chat
172
  new_current_chat_plot_id = None # Clear the chat context
 
173
  else: # Toggling ON a panel action
174
  new_active_action_state_to_set = hypothetical_new_active_state
175
  action_col_visible_update = gr.update(visible=True)
 
189
  is_active_formula = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "formula"}
190
  generated_bomb_btn_updates.append(gr.update(value=self.ACTIVE_ICON if is_active_insights else self.BOMB_ICON))
191
  generated_formula_btn_updates.append(gr.update(value=self.ACTIVE_ICON if is_active_formula else self.FORMULA_ICON))
192
+
193
  if action_type == "insights":
194
  insights_chatbot_visible_update = gr.update(visible=True)
195
  insights_chat_input_visible_update = gr.update(visible=True)
196
  insights_suggestions_row_visible_update = gr.update(visible=True)
197
  new_current_chat_plot_id = plot_id_clicked # Set chat context to this plot
 
198
  history = current_chat_histories.get(plot_id_clicked, [])
199
  summary = current_plot_data_for_chatbot.get(plot_id_clicked, f"No summary available for '{clicked_plot_label}'.")
200
+
201
  if not history: # First time opening chat for this plot
202
  prompt, sugg = self.get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
203
  llm_history_for_api = [{"role": "user", "content": prompt}] # API expects list of dicts
 
206
  updated_chat_histories = {**current_chat_histories, plot_id_clicked: history}
207
  else: # Re-opening chat, just get new suggestions if any
208
  _, sugg = self.get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
209
+
210
  chatbot_content_update = gr.update(value=history)
211
  s1_upd = gr.update(value=sugg[0] if sugg and len(sugg) > 0 else "N/A")
212
  s2_upd = gr.update(value=sugg[1] if sugg and len(sugg) > 1 else "N/A")
 
228
 
229
  # Order of updates must match self.action_panel_outputs_list
230
  final_updates = [
231
+ action_col_visible_update, # global_actions_column_ui
232
+ insights_chatbot_visible_update, # insights_chatbot_ui (visibility)
233
+ chatbot_content_update, # insights_chatbot_ui (content)
234
  insights_chat_input_visible_update, # insights_chat_input_ui
235
  insights_suggestions_row_visible_update, # insights_suggestions_row_ui
236
+ s1_upd, s2_upd, s3_upd, # suggestion buttons
237
+ formula_display_visible_update, # formula_display_markdown_ui (visibility)
238
+ formula_content_update, # formula_display_markdown_ui (content)
239
+ formula_close_hint_visible_update, # formula_close_hint_md
240
+ new_active_action_state_to_set, # active_panel_action_state
241
+ new_current_chat_plot_id, # current_chat_plot_id_st
242
+ updated_chat_histories, # chat_histories_st
243
+ new_explored_plot_id_to_set # explored_plot_id_state
244
  ]
245
  final_updates.extend(generated_panel_vis_updates) # Plot panel visibilities
246
  final_updates.extend(generated_bomb_btn_updates) # Bomb button icons
247
  final_updates.extend(generated_formula_btn_updates) # Formula button icons
248
  final_updates.extend(generated_explore_btn_updates) # Explore button icons
249
  final_updates.extend(section_title_vis_updates) # Section title visibilities
250
+
251
  logging.debug(f"handle_panel_action returning {len(final_updates)} updates. Expected {15 + 4*len(PLOT_CONFIGS) + NUM_UNIQUE_SECTIONS}.")
252
  return final_updates
253
 
 
263
 
264
  history_for_api = chat_histories.get(current_plot_id, []).copy()
265
  if not isinstance(history_for_api, list): history_for_api = []
 
266
  history_for_api.append({"role": "user", "content": user_message})
267
+
268
  current_display_history = history_for_api.copy()
 
269
  yield current_display_history, gr.update(value=""), chat_histories
270
 
271
  assistant_response = await self.generate_llm_response(user_message, current_plot_id, plot_label, history_for_api, summary_data)
 
272
 
273
+ history_for_api.append({"role": "assistant", "content": assistant_response})
274
  updated_chat_histories = {**chat_histories, current_plot_id: history_for_api}
275
  current_display_history.append({"role": "assistant", "content": assistant_response})
276
+
277
  yield current_display_history, "", updated_chat_histories
278
 
279
  async def _handle_suggested_question_click(self, suggestion_text: str, current_plot_id: str, chat_histories: dict, current_plot_data_for_chatbot: dict):
 
281
  current_history_for_plot = chat_histories.get(current_plot_id, [])
282
  yield current_history_for_plot, gr.update(value=""), chat_histories
283
  return
284
+
285
  async for update_chunk in self._handle_chat_message_submission(suggestion_text, current_plot_id, chat_histories, current_plot_data_for_chatbot):
286
  yield update_chunk
287
 
 
299
 
300
  new_explored_id_to_set = None
301
  is_toggling_off_explore = (plot_id_clicked == current_explored_plot_id_from_state)
302
+
303
  action_col_upd = gr.update()
304
  new_active_panel_state_upd = current_active_panel_action_state
305
  formula_hint_upd = gr.update(visible=False)
306
+
307
  panel_vis_updates = []
308
  explore_btns_updates = []
309
  bomb_btns_updates = [gr.update()] * num_plots
 
324
  else:
325
  new_explored_id_to_set = plot_id_clicked
326
  logging.info(f"Exploring {plot_id_clicked}. Hiding other plots/sections.")
 
327
  for i, sec_name in enumerate(UNIQUE_ORDERED_SECTIONS):
328
  section_title_vis_updates[i] = gr.update(visible=(sec_name == section_of_clicked_plot))
329
 
 
331
  is_target_plot = (cfg["id"] == new_explored_id_to_set)
332
  panel_vis_updates.append(gr.update(visible=is_target_plot))
333
  explore_btns_updates.append(gr.update(value=self.ACTIVE_ICON if is_target_plot else self.EXPLORE_ICON))
334
+
335
  if current_active_panel_action_state:
336
  logging.info("Closing active insight/formula panel due to explore click.")
337
  action_col_upd = gr.update(visible=False)
 
351
  final_explore_updates.extend(bomb_btns_updates)
352
  final_explore_updates.extend(formula_btns_updates)
353
  final_explore_updates.extend(section_title_vis_updates)
354
+
355
  logging.debug(f"handle_explore_click returning {len(final_explore_updates)} updates. Expected {4 + 4*len(PLOT_CONFIGS) + NUM_UNIQUE_SECTIONS}.")
356
  return final_explore_updates
357
 
 
359
  async def _handler(curr_active_val, curr_chats_val, curr_chat_pid, curr_plot_data, curr_explored_id):
360
  return await self._handle_panel_action(p_id, action_type_str, curr_active_val, curr_chats_val, curr_chat_pid, curr_plot_data, curr_explored_id)
361
  return _handler
362
+
 
 
 
363
  async def _refresh_analytics_graphs_ui(self, current_token_state_val, date_filter_val, custom_start_val, custom_end_val, current_chat_histories_val):
364
  logging.info("Refreshing analytics graph UI elements and resetting actions/chat (within module).")
365
  start_time = time.time()
 
 
 
 
 
 
 
366
 
367
+ plot_gen_results = self.update_analytics_plots_figures(current_token_state_val, date_filter_val, custom_start_val, custom_end_val, PLOT_CONFIGS)
368
+
369
  try:
370
  expected_count = len(PLOT_CONFIGS) + 2
371
  if len(plot_gen_results) == expected_count:
 
379
  status_msg = f"Errore: L'aggiornamento dei grafici non Γ¨ riuscito a causa di un formato di dati imprevisto."
380
  gen_figs = [self.create_placeholder_plot("Error", f"Dati non validi {i}") for i in range(len(PLOT_CONFIGS))]
381
  new_summaries_for_chatbot = {}
382
+
383
  all_updates = []
384
  # 1. Status Markdown
385
  all_updates.append(status_msg) # For self.analytics_status_md
386
+
387
  # 2. Plot components
388
  if isinstance(gen_figs, list) and len(gen_figs) == len(PLOT_CONFIGS):
389
  all_updates.extend(gen_figs)
390
  else:
391
  logging.error(f"Mismatch in generated figures ({len(gen_figs) if isinstance(gen_figs, list) else 'N/A'}) and plot_configs ({len(PLOT_CONFIGS)})")
392
  all_updates.extend([self.create_placeholder_plot("Error", f"Figura mancante {i}") for i in range(len(PLOT_CONFIGS))])
393
+
394
  # 3. UI Resets for Action Panel (9 components)
395
  all_updates.extend([
396
  gr.update(visible=False), # global_actions_column_ui
 
401
  gr.update(value="I dettagli sulla formula/metodologia appariranno qui.", visible=False), # formula_display_markdown_ui
402
  gr.update(visible=False) # formula_close_hint_md
403
  ])
404
+
405
  # 4. State Resets (4 states)
406
  all_updates.extend([
407
  None, # active_panel_action_state (reset)
 
409
  {}, # chat_histories_st (reset all chats on refresh)
410
  new_summaries_for_chatbot # plot_data_for_chatbot_st (update with new summaries)
411
  ])
412
+
413
  # 5. Plot-specific UI Resets (4 components per plot)
414
  for _ in PLOT_CONFIGS:
415
  all_updates.extend([
416
  gr.update(value=self.BOMB_ICON), # bomb_button
417
  gr.update(value=self.FORMULA_ICON), # formula_button
418
  gr.update(value=self.EXPLORE_ICON), # explore_button
419
+ gr.update(visible=True) # panel_component (plot visibility itself)
420
  ])
421
+
422
  # 6. Explored Plot ID State Reset (1 state)
423
  all_updates.append(None) # explored_plot_id_state (reset)
424
+
425
  # 7. Section Title Visibilities
426
  all_updates.extend([gr.update(visible=True)] * NUM_UNIQUE_SECTIONS)
427
+
428
  end_time = time.time()
429
  logging.info(f"Analytics graph refresh (module) took {end_time - start_time:.2f} seconds.")
430
+
431
  expected_len = 1 + len(PLOT_CONFIGS) + 9 + 4 + (4 * len(PLOT_CONFIGS)) + 1 + NUM_UNIQUE_SECTIONS
432
  logging.info(f"Prepared {len(all_updates)} updates for graph refresh. Expected {expected_len}.")
433
  if len(all_updates) != expected_len:
434
  logging.error(f"Output length mismatch in _refresh_analytics_graphs_ui: got {len(all_updates)}, expected {expected_len}")
435
+
436
  return tuple(all_updates)
437
 
438
  def _define_callback_outputs(self):
 
496
  graph_refresh_inputs = [
497
  self.token_state, self.date_filter_selector,
498
  self.custom_start_date_picker, self.custom_end_date_picker,
499
+ self.chat_histories_st
500
+ ]
501
  self.apply_filter_btn.click(
502
  fn=self._refresh_analytics_graphs_ui,
503
  inputs=graph_refresh_inputs,
 
505
  show_progress="full",
506
  api_name="refresh_analytics_graphs_module"
507
  )
508
+
509
  # Panel action buttons (bomb, formula, explore)
510
  action_click_inputs = [
511
  self.active_panel_action_state, self.chat_histories_st,
 
541
  )
542
  else:
543
  logging.warning(f"UI object for plot_id '{plot_id}' not found for click handlers in module.")
544
+
545
  # Chat submission
546
  chat_submission_outputs = [self.insights_chatbot_ui, self.insights_chat_input_ui, self.chat_histories_st]
547
  chat_submission_inputs = [
 
580
 
581
  def create_tab_ui(self):
582
  with gr.TabItem("πŸ“Š Grafici", id="tab_analytics_module"):
 
583
  gr.Markdown("## πŸ“ˆ Analisi Performance LinkedIn")
584
  gr.Markdown("Seleziona un intervallo di date per i grafici. Clicca i pulsanti (πŸ’£ Insights, Ζ’ Formula, 🧭 Esplora) su un grafico per azioni.")
585
  self.analytics_status_md = gr.Markdown("Stato analisi grafici...")
 
586
  with gr.Row():
587
  self.date_filter_selector = gr.Radio(
588
  ["Sempre", "Ultimi 7 Giorni", "Ultimi 30 Giorni", "Intervallo Personalizzato"],
589
  label="Seleziona Intervallo Date per Grafici", value="Sempre", scale=3
590
  )
591
  with gr.Column(scale=2):
592
+ # MODIFIED: Replaced gr.DateTime with the more reliable Calendar component
593
+ self.custom_start_date_picker = Calendar(label="Data Inizio", visible=False, type="datetime")
594
+ self.custom_end_date_picker = Calendar(label="Data Fine", visible=False, type="datetime")
 
595
 
596
  self.apply_filter_btn = gr.Button("πŸ” Applica Filtro & Aggiorna Grafici", variant="primary")
597
+
598
  self.date_filter_selector.change(
599
  fn=self._toggle_custom_date_pickers,
600
  inputs=[self.date_filter_selector],
 
633
  self.insights_suggestion_1_btn = gr.Button(value="Suggerimento 1", size="sm", min_width=50)
634
  self.insights_suggestion_2_btn = gr.Button(value="Suggerimento 2", size="sm", min_width=50)
635
  self.insights_suggestion_3_btn = gr.Button(value="Suggerimento 3", size="sm", min_width=50)
636
+
637
  self.formula_display_markdown_ui = gr.Markdown(
638
  "I dettagli sulla formula/metodologia appariranno qui.", visible=False
639
  )
 
641
  "<p style='font-size:0.9em; text-align:center; margin-top:10px;'><em>Click the active Ζ’ button on the plot again to close this panel.</em></p>",
642
  visible=False
643
  )
644
+
645
  self._define_callback_outputs()
646
  self._setup_callbacks()