Spaces:
Running
Running
Update services/analytics_tab_module.py
Browse files- 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,
|
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,
|
255 |
-
insights_chatbot_visible_update,
|
256 |
-
chatbot_content_update,
|
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,
|
260 |
-
formula_display_visible_update,
|
261 |
-
formula_content_update,
|
262 |
-
formula_close_hint_visible_update,
|
263 |
-
new_active_action_state_to_set,
|
264 |
-
new_current_chat_plot_id,
|
265 |
-
updated_chat_histories,
|
266 |
-
new_explored_plot_id_to_set
|
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)
|
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 |
-
#
|
627 |
-
|
628 |
-
self.
|
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()
|