GuglielmoTor commited on
Commit
cb60e91
·
verified ·
1 Parent(s): 29818ba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +219 -120
app.py CHANGED
@@ -1,5 +1,6 @@
1
  # app.py
2
  # (Showing relevant parts that need modification)
 
3
  import gradio as gr
4
  import pandas as pd
5
  import os
@@ -135,30 +136,34 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
135
  unique_ordered_sections = list(OrderedDict.fromkeys(pc["section"] for pc in plot_configs))
136
  num_unique_sections = len(unique_ordered_sections)
137
 
138
- active_panel_action_state = gr.State(None)
139
- explored_plot_id_state = gr.State(None)
140
 
141
  plot_ui_objects = {}
142
  section_titles_map = {}
143
 
144
  with gr.Row(equal_height=False):
145
  with gr.Column(scale=8) as plots_area_col:
146
- ui_elements_tuple = build_analytics_tab_plot_area(plot_configs) # This function is in ui_generators.py
 
147
  if isinstance(ui_elements_tuple, tuple) and len(ui_elements_tuple) == 2:
148
  plot_ui_objects, section_titles_map = ui_elements_tuple
 
149
  if not all(sec_name in section_titles_map for sec_name in unique_ordered_sections):
150
  logging.error("section_titles_map from build_analytics_tab_plot_area is incomplete.")
 
151
  for sec_name in unique_ordered_sections:
152
  if sec_name not in section_titles_map:
 
153
  section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)")
154
  else:
155
- logging.error("build_analytics_tab_plot_area did not return (plot_ui_objects, section_titles_map).")
156
  plot_ui_objects = ui_elements_tuple if isinstance(ui_elements_tuple, dict) else {} # Fallback
157
  for sec_name in unique_ordered_sections: # Fallback for section titles
158
  section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)")
159
 
160
 
161
- with gr.Column(scale=4, visible=False) as global_actions_column_ui:
162
  gr.Markdown("### 💡 Azioni Contestuali Grafico")
163
  insights_chatbot_ui = gr.Chatbot(
164
  label="Chat Insights", type="messages", height=450,
@@ -194,17 +199,20 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
194
  clicked_plot_config = next((p for p in plot_configs if p["id"] == plot_id_clicked), None)
195
  if not clicked_plot_config:
196
  logging.error(f"Config not found for plot_id {plot_id_clicked}")
197
- # Ensure this error return matches the expected number of outputs
198
  num_plots = len(plot_configs)
199
- # Count for action_panel_outputs_list: 11 base UI + 4 states + N panels + 3N buttons + M sections = 15 + 4N + M
200
- # 15 + 4*19 + 6 = 15 + 76 + 6 = 97
201
- error_list_len = 11 + 4 + num_plots + (3 * num_plots) + num_unique_sections # Matches action_panel_outputs_list structure
202
  error_list = [gr.update()] * error_list_len
203
  # Manually set key states to avoid further issues if possible
204
- error_list[11] = current_active_action_from_state # active_panel_action_state
205
- error_list[12] = current_chat_plot_id # current_chat_plot_id_st
206
- error_list[13] = current_chat_histories # chat_histories_st
207
- error_list[14] = current_explored_plot_id # explored_plot_id_state
 
 
 
 
 
208
  return error_list
209
 
210
  clicked_plot_label = clicked_plot_config["label"]
@@ -212,6 +220,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
212
  hypothetical_new_active_state = {"plot_id": plot_id_clicked, "type": action_type}
213
  is_toggling_off = current_active_action_from_state == hypothetical_new_active_state
214
 
 
215
  action_col_visible_update = gr.update(visible=False)
216
  insights_chatbot_visible_update, insights_chat_input_visible_update, insights_suggestions_row_visible_update = gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
217
  formula_display_visible_update = gr.update(visible=False)
@@ -221,62 +230,81 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
221
  new_active_action_state_to_set, new_current_chat_plot_id = None, current_chat_plot_id
222
  updated_chat_histories, new_explored_plot_id_to_set = current_chat_histories, current_explored_plot_id
223
 
224
- panel_vis_updates, action_btn_updates, explore_btn_updates = [], [], []
 
 
 
 
225
  section_title_vis_updates = [gr.update()] * num_unique_sections
226
 
 
227
  if is_toggling_off:
228
- new_active_action_state_to_set = None
229
- action_col_visible_update = gr.update(visible=False)
230
  logging.info(f"Toggling OFF panel {action_type} for {plot_id_clicked}.")
 
 
 
 
 
 
231
  if current_explored_plot_id:
 
232
  explored_cfg = next((p for p in plot_configs if p["id"] == current_explored_plot_id), None)
233
  explored_sec = explored_cfg["section"] if explored_cfg else None
234
  for i, sec_name in enumerate(unique_ordered_sections):
235
  section_title_vis_updates[i] = gr.update(visible=(sec_name == explored_sec))
236
  for cfg in plot_configs:
237
  is_exp = (cfg["id"] == current_explored_plot_id)
238
- panel_vis_updates.append(gr.update(visible=is_exp))
239
- explore_btn_updates.append(gr.update(value=ACTIVE_ICON if is_exp else EXPLORE_ICON))
240
  else:
 
241
  for i in range(num_unique_sections): section_title_vis_updates[i] = gr.update(visible=True)
242
  for _ in plot_configs:
243
- panel_vis_updates.append(gr.update(visible=True))
244
- explore_btn_updates.append(gr.update(value=EXPLORE_ICON))
245
- for _ in plot_configs:
246
- action_btn_updates.extend([gr.update(value=BOMB_ICON), gr.update(value=FORMULA_ICON)])
247
- if action_type == "insights": new_current_chat_plot_id = None
248
 
249
- else:
250
  new_active_action_state_to_set = hypothetical_new_active_state
251
- action_col_visible_update = gr.update(visible=True)
252
- new_explored_plot_id_to_set = None
253
- logging.info(f"Toggling ON panel {action_type} for {plot_id_clicked}. Cancelling explore view.")
254
 
 
255
  for i, sec_name in enumerate(unique_ordered_sections):
256
  section_title_vis_updates[i] = gr.update(visible=(sec_name == clicked_plot_section))
257
  for cfg in plot_configs:
258
- panel_vis_updates.append(gr.update(visible=(cfg["id"] == plot_id_clicked)))
259
- explore_btn_updates.append(gr.update(value=EXPLORE_ICON))
260
-
 
261
  for cfg_btn in plot_configs:
262
  is_act_ins = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "insights"}
263
  is_act_for = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "formula"}
264
- action_btn_updates.extend([gr.update(value=ACTIVE_ICON if is_act_ins else BOMB_ICON),
265
- gr.update(value=ACTIVE_ICON if is_act_for else FORMULA_ICON)])
 
266
  if action_type == "insights":
267
  insights_chatbot_visible_update, insights_chat_input_visible_update, insights_suggestions_row_visible_update = gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)
268
  new_current_chat_plot_id = plot_id_clicked
269
  history = current_chat_histories.get(plot_id_clicked, [])
270
  summary = current_plot_data_for_chatbot.get(plot_id_clicked, f"No summary for '{clicked_plot_label}'.")
271
- if not history:
272
  prompt, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
273
  llm_hist = [{"role": "user", "content": prompt}]
 
274
  resp = await generate_llm_response(prompt, plot_id_clicked, clicked_plot_label, llm_hist, summary)
275
- history = [{"role": "assistant", "content": resp}]
276
  updated_chat_histories = {**current_chat_histories, plot_id_clicked: history}
277
- else: _, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
 
 
278
  chatbot_content_update = gr.update(value=history)
279
  s1_upd,s2_upd,s3_upd = gr.update(value=sugg[0] if sugg else "N/A"),gr.update(value=sugg[1] if len(sugg)>1 else "N/A"),gr.update(value=sugg[2] if len(sugg)>2 else "N/A")
 
280
  elif action_type == "formula":
281
  formula_display_visible_update = gr.update(visible=True)
282
  formula_close_hint_visible_update = gr.update(visible=True)
@@ -287,108 +315,155 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
287
  f_text += f"### {f_data['title']}\n\n{f_data['description']}\n\n**Calculation:**\n" + "\n".join([f"- {s}" for s in f_data['calculation_steps']])
288
  else: f_text += "(No detailed formula information found.)"
289
  formula_content_update = gr.update(value=f_text)
290
- new_current_chat_plot_id = None
291
 
 
292
  final_updates = [
293
  action_col_visible_update, insights_chatbot_visible_update, chatbot_content_update,
294
  insights_chat_input_visible_update, insights_suggestions_row_visible_update,
295
  s1_upd, s2_upd, s3_upd, formula_display_visible_update, formula_content_update,
296
- formula_close_hint_visible_update,
 
297
  new_active_action_state_to_set, new_current_chat_plot_id, updated_chat_histories,
298
  new_explored_plot_id_to_set
299
  ]
300
- final_updates.extend(panel_vis_updates)
301
- final_updates.extend(action_btn_updates) # Should be 2*N items
302
- final_updates.extend(explore_btn_updates) # Should be N items
303
- final_updates.extend(section_title_vis_updates)
304
- # Total: 11 (base UI) + 4 (states) + N (panels) + 2N (action_btns) + N (explore_btns) + M (sections)
305
- # = 15 + 4N + M. For N=19, M=6: 15 + 76 + 6 = 97. This matches action_panel_outputs_list.
 
 
 
306
  return final_updates
307
 
308
  async def handle_chat_message_submission(user_message: str, current_plot_id: str, chat_histories: dict, current_plot_data_for_chatbot: dict ):
309
  if not current_plot_id or not user_message.strip():
310
- yield chat_histories.get(current_plot_id, []), gr.update(value=""), chat_histories; return
 
 
 
 
311
  cfg = next((p for p in plot_configs if p["id"] == current_plot_id), None)
312
  lbl = cfg["label"] if cfg else "Selected Plot"
313
  summary = current_plot_data_for_chatbot.get(current_plot_id, f"No summary for '{lbl}'.")
314
- hist = chat_histories.get(current_plot_id, []).copy() + [{"role": "user", "content": user_message}]
315
- yield hist, gr.update(value=""), chat_histories
 
 
 
 
 
 
 
316
  resp = await generate_llm_response(user_message, current_plot_id, lbl, hist, summary)
317
  hist.append({"role": "assistant", "content": resp})
318
- yield hist, "", {**chat_histories, current_plot_id: hist}
 
 
 
 
319
 
320
  async def handle_suggested_question_click(suggestion_text: str, current_plot_id: str, chat_histories: dict, current_plot_data_for_chatbot: dict):
321
- if not current_plot_id or not suggestion_text.strip():
322
- yield chat_histories.get(current_plot_id, []), gr.update(value=""), chat_histories; return
323
- async for update in handle_chat_message_submission(suggestion_text, current_plot_id, chat_histories, current_plot_data_for_chatbot): yield update
 
 
 
 
 
 
324
 
325
  def handle_explore_click(plot_id_clicked, current_explored_plot_id_from_state, current_active_panel_action_state):
326
  logging.info(f"Explore Click: Plot '{plot_id_clicked}'. Current Explored: {current_explored_plot_id_from_state}. Active Panel: {current_active_panel_action_state}")
327
  num_plots = len(plot_configs)
328
  if not plot_ui_objects:
329
  logging.error("plot_ui_objects not populated for handle_explore_click.")
330
- # explore_outputs_list: 4 base states/UI + N panels + N explore_btns + 2N action_btns + M sections
331
- # = 4 + 4N + M. For N=19, M=6: 4 + 76 + 6 = 86
332
- error_list_len = 4 + num_plots + num_plots + (2 * num_plots) + num_unique_sections
333
  error_list = [gr.update()] * error_list_len
334
- error_list[0] = current_explored_plot_id_from_state
335
- error_list[2] = current_active_panel_action_state
336
  return error_list
337
 
 
 
338
 
339
- new_explored_id_to_set, is_toggling_off_explore = None, (plot_id_clicked == current_explored_plot_id_from_state)
340
- action_col_upd, new_active_panel_state_upd = gr.update(), current_active_panel_action_state
341
- panel_vis, explore_btns, bomb_btns, formula_btns = [], [], [], []
342
- section_title_vis = [gr.update()] * num_unique_sections
343
- formula_hint_upd = gr.update(visible=False)
 
 
 
 
344
 
345
  clicked_cfg = next((p for p in plot_configs if p["id"] == plot_id_clicked), None)
346
  sec_of_clicked = clicked_cfg["section"] if clicked_cfg else None
347
 
348
  if is_toggling_off_explore:
349
- new_explored_id_to_set = None
350
- logging.info(f"Stopping explore for {plot_id_clicked}. All plots/sections visible.")
351
- for i in range(num_unique_sections): section_title_vis[i] = gr.update(visible=True)
352
  for _ in plot_configs:
353
- panel_vis.append(gr.update(visible=True)); explore_btns.append(gr.update(value=EXPLORE_ICON))
354
- bomb_btns.append(gr.update()); formula_btns.append(gr.update())
355
- else:
 
 
 
 
356
  new_explored_id_to_set = plot_id_clicked
357
  logging.info(f"Exploring {plot_id_clicked}. Hiding other plots/sections.")
358
- for i, sec_name in enumerate(unique_ordered_sections): section_title_vis[i] = gr.update(visible=(sec_name == sec_of_clicked))
359
- for cfg_idx, cfg in enumerate(plot_configs): # Iterate with index for bomb_btns/formula_btns
 
360
  is_target = (cfg["id"] == new_explored_id_to_set)
361
- panel_vis.append(gr.update(visible=is_target))
362
- explore_btns.append(gr.update(value=ACTIVE_ICON if is_target else EXPLORE_ICON))
363
- # Initialize bomb_btns and formula_btns correctly for all plots
364
- if current_active_panel_action_state and current_active_panel_action_state.get("plot_id") == cfg["id"]:
365
- # This should not happen if we are closing active panel below
366
- pass # Will be overridden
367
-
368
- if current_active_panel_action_state:
369
  logging.info("Closing active insight/formula panel due to explore click.")
370
- action_col_upd = gr.update(visible=False); new_active_panel_state_upd = None
371
- # Reset all bomb and formula buttons
372
- bomb_btns = [gr.update(value=BOMB_ICON) for _ in plot_configs]
373
- formula_btns = [gr.update(value=FORMULA_ICON) for _ in plot_configs]
374
- else:
375
- # If no panel was active, keep current state of bomb/formula buttons (no update)
376
- bomb_btns = [gr.update() for _ in plot_configs]
377
- formula_btns = [gr.update() for _ in plot_configs]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
 
379
- # Order: states (4), panels (N), explore_btns (N), bomb_btns (N), formula_btns (N), section_titles (M)
380
- # Total: 4 + N + N + N + N + M = 4 + 4N + M.
381
- return [new_explored_id_to_set, action_col_upd, new_active_panel_state_upd, formula_hint_upd] + \
382
- panel_vis + explore_btns + bomb_btns + formula_btns + section_title_vis
383
 
384
  # --- Define Output Lists for Event Handlers ---
385
  # Base UI for action panel (insights/formula): 11 elements
386
  _base_action_panel_ui_outputs = [
387
- global_actions_column_ui, insights_chatbot_ui, insights_chatbot_ui,
388
  insights_chat_input_ui, insights_suggestions_row_ui,
389
  insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn,
390
  formula_display_markdown_ui, formula_display_markdown_ui, # Formula MD (vis + value)
391
- formula_close_hint_md
392
  ]
393
  # States for action panel: 4 elements
394
  _action_panel_state_outputs = [active_panel_action_state, current_chat_plot_id_st, chat_histories_st, explored_plot_id_state]
@@ -399,7 +474,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
399
  action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs]) # +N (formula_button)
400
  action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("explore_button", gr.update()) for pc in plot_configs]) # +N (explore_button)
401
  action_panel_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections]) # +M (sections)
402
- # Total for action_panel_outputs_list = 15 + 4N + M. For N=19, M=6: 15 + 76 + 6 = 97.
403
 
404
  # Base UI/States for explore click: 4 elements
405
  _explore_base_outputs = [explored_plot_id_state, global_actions_column_ui, active_panel_action_state, formula_close_hint_md]
@@ -409,7 +484,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
409
  explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("bomb_button", gr.update()) for pc in plot_configs]) # +N (bomb_button)
410
  explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs]) # +N (formula_button)
411
  explore_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections]) # +M (sections)
412
- # Total for explore_outputs_list = 4 + 4N + M. For N=19, M=6: 4 + 76 + 6 = 86.
413
 
414
  action_click_inputs = [active_panel_action_state, chat_histories_st, current_chat_plot_id_st, plot_data_for_chatbot_st, explored_plot_id_state]
415
  explore_click_inputs = [explored_plot_id_state, active_panel_action_state]
@@ -423,52 +498,76 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
423
  plot_id = config_item["id"]
424
  if plot_id in plot_ui_objects:
425
  ui_obj = plot_ui_objects[plot_id]
426
- ui_obj["bomb_button"].click(fn=create_panel_action_handler(plot_id, "insights"), inputs=action_click_inputs, outputs=action_panel_outputs_list, api_name=f"action_insights_{plot_id}")
427
- ui_obj["formula_button"].click(fn=create_panel_action_handler(plot_id, "formula"), inputs=action_click_inputs, outputs=action_panel_outputs_list, api_name=f"action_formula_{plot_id}")
428
- ui_obj["explore_button"].click(fn=lambda current_explored_val, current_active_panel_val, p_id=plot_id: handle_explore_click(p_id, current_explored_val, current_active_panel_val), inputs=explore_click_inputs, outputs=explore_outputs_list, api_name=f"action_explore_{plot_id}")
 
 
 
 
429
  else: logging.warning(f"UI object for plot_id '{plot_id}' not found for click handlers.")
430
 
 
431
  chat_submission_outputs = [insights_chatbot_ui, insights_chat_input_ui, chat_histories_st]
432
  chat_submission_inputs = [insights_chat_input_ui, current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
433
  insights_chat_input_ui.submit(fn=handle_chat_message_submission, inputs=chat_submission_inputs, outputs=chat_submission_outputs, api_name="submit_chat_message")
434
- suggestion_click_inputs = [current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
435
- insights_suggestion_1_btn.click(fn=handle_suggested_question_click, inputs=[insights_suggestion_1_btn] + suggestion_click_inputs, outputs=chat_submission_outputs, api_name="click_suggestion_1")
436
- insights_suggestion_2_btn.click(fn=handle_suggested_question_click, inputs=[insights_suggestion_2_btn] + suggestion_click_inputs, outputs=chat_submission_outputs, api_name="click_suggestion_2")
437
- insights_suggestion_3_btn.click(fn=handle_suggested_question_click, inputs=[insights_suggestion_3_btn] + suggestion_click_inputs, outputs=chat_submission_outputs, api_name="click_suggestion_3")
 
 
438
 
439
  def refresh_all_analytics_ui_elements(current_token_state, date_filter_val, custom_start_val, custom_end_val, current_chat_histories):
440
  logging.info("Refreshing all analytics UI elements and resetting actions/chat.")
441
  plot_gen_results = update_analytics_plots_figures(current_token_state, date_filter_val, custom_start_val, custom_end_val, plot_configs)
442
  status_msg, gen_figs, new_summaries = plot_gen_results[0], plot_gen_results[1:-1], plot_gen_results[-1]
443
 
444
- all_updates = [status_msg] # 1
445
- all_updates.extend(gen_figs if len(gen_figs) == len(plot_configs) else [create_placeholder_plot("Error", f"Fig missing {i}") for i in range(len(plot_configs))]) # +N (19)
446
 
447
- # UI Resets (9 elements) + State Resets (4 elements) = 13 elements
448
  all_updates.extend([
449
- gr.update(visible=False), gr.update(value=[], visible=False), gr.update(value="", visible=False), # global_actions_col, chatbot, chat_input
450
- gr.update(visible=False), gr.update(value="S1"), gr.update(value="S2"), gr.update(value="S3"), # suggestions_row, sugg_btns
451
- gr.update(value="Formula details here.", visible=False), gr.update(visible=False), # formula_md, formula_hint_md
452
- None, None, {}, new_summaries # active_panel_state, current_chat_plot_id, chat_histories, plot_data_for_chatbot
453
- ]) # +13
 
 
 
454
 
455
- for _ in plot_configs: # Reset button icons (3) & panel visibility (1) = 4 per plot
456
- all_updates.extend([gr.update(value=BOMB_ICON), gr.update(value=FORMULA_ICON), gr.update(value=EXPLORE_ICON), gr.update(visible=True)]) # +4N (76)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
457
 
458
  all_updates.append(None) # Reset explored_plot_id_state (1 element)
459
- all_updates.extend([gr.update(visible=True)] * num_unique_sections) # Make all section titles visible (M = 6 elements)
460
- # Total: 1 + N + 13 + 4N + 1 + M = 1 + 19 + 13 + 76 + 1 + 6 = 116. This matches apply_filter_and_sync_outputs_list.
461
- logging.info(f"Prepared {len(all_updates)} updates for analytics refresh.")
 
 
 
462
  return all_updates
463
 
464
  # Construction of apply_filter_and_sync_outputs_list
465
- # This list defines the components Gradio expects to be updated.
466
- # Its length must match the number of items returned by refresh_all_analytics_ui_elements.
467
-
468
  apply_filter_and_sync_outputs_list = [analytics_status_md] # 1. Status
469
  apply_filter_and_sync_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("plot_component", gr.update()) for pc in plot_configs]) # 2. Plot figures (N)
470
 
471
- # 3. UI Resets (9 elements for action panel)
472
  _ui_resets_for_filter = [
473
  global_actions_column_ui, insights_chatbot_ui, insights_chat_input_ui,
474
  insights_suggestions_row_ui, insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn,
@@ -487,7 +586,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
487
  plot_ui_objects.get(pid, {}).get("bomb_button", gr.update()),
488
  plot_ui_objects.get(pid, {}).get("formula_button", gr.update()),
489
  plot_ui_objects.get(pid, {}).get("explore_button", gr.update()),
490
- plot_ui_objects.get(pid, {}).get("panel_component", gr.update())
491
  ])
492
 
493
  # 6. Explored state reset (1 element)
@@ -495,8 +594,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
495
 
496
  # 7. Section Titles (M elements)
497
  apply_filter_and_sync_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections])
498
- # Total elements: 1 + N + 9 + 4 + 4N + 1 + M = 15 + 5N + M
499
- # For N=19, M=6: 15 + 5*19 + 6 = 15 + 95 + 6 = 110 + 6 = 116. This is correct.
500
 
501
  apply_filter_btn.click(
502
  fn=refresh_all_analytics_ui_elements,
@@ -534,10 +632,11 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
534
  sync_event_part3 = sync_event_part2.then(fn=display_main_dashboard, inputs=[token_state], outputs=[dashboard_display_html], show_progress=False)
535
  sync_event_final = sync_event_part3.then(fn=refresh_all_analytics_ui_elements, inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st], outputs=apply_filter_and_sync_outputs_list, show_progress="full")
536
 
 
537
  if __name__ == "__main__":
538
  if not os.environ.get(LINKEDIN_CLIENT_ID_ENV_VAR): logging.warning(f"ATTENZIONE: '{LINKEDIN_CLIENT_ID_ENV_VAR}' non impostata.")
539
  if not all(os.environ.get(var) for var in [BUBBLE_APP_NAME_ENV_VAR, BUBBLE_API_KEY_PRIVATE_ENV_VAR, BUBBLE_API_ENDPOINT_ENV_VAR]):
540
  logging.warning("ATTENZIONE: Variabili Bubble non impostate.")
541
  try: logging.info(f"Matplotlib: {matplotlib.__version__}, Backend: {matplotlib.get_backend()}")
542
  except ImportError: logging.warning("Matplotlib non trovato.")
543
- app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
 
1
  # app.py
2
  # (Showing relevant parts that need modification)
3
+
4
  import gradio as gr
5
  import pandas as pd
6
  import os
 
136
  unique_ordered_sections = list(OrderedDict.fromkeys(pc["section"] for pc in plot_configs))
137
  num_unique_sections = len(unique_ordered_sections)
138
 
139
+ active_panel_action_state = gr.State(None) # Stores {"plot_id": "...", "type": "insights/formula"}
140
+ explored_plot_id_state = gr.State(None) # Stores plot_id of the currently explored plot
141
 
142
  plot_ui_objects = {}
143
  section_titles_map = {}
144
 
145
  with gr.Row(equal_height=False):
146
  with gr.Column(scale=8) as plots_area_col:
147
+ # This function is in ui_generators.py and now builds the entire plot area
148
+ ui_elements_tuple = build_analytics_tab_plot_area(plot_configs)
149
  if isinstance(ui_elements_tuple, tuple) and len(ui_elements_tuple) == 2:
150
  plot_ui_objects, section_titles_map = ui_elements_tuple
151
+ # Validate section_titles_map
152
  if not all(sec_name in section_titles_map for sec_name in unique_ordered_sections):
153
  logging.error("section_titles_map from build_analytics_tab_plot_area is incomplete.")
154
+ # Fallback for missing section titles
155
  for sec_name in unique_ordered_sections:
156
  if sec_name not in section_titles_map:
157
+ # This should not happen if build_analytics_tab_plot_area is correct
158
  section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)")
159
  else:
160
+ logging.error("build_analytics_tab_plot_area did not return a tuple of (plot_ui_objects, section_titles_map).")
161
  plot_ui_objects = ui_elements_tuple if isinstance(ui_elements_tuple, dict) else {} # Fallback
162
  for sec_name in unique_ordered_sections: # Fallback for section titles
163
  section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)")
164
 
165
 
166
+ with gr.Column(scale=4, visible=False) as global_actions_column_ui: # Right-hand column for chatbot/formula
167
  gr.Markdown("### 💡 Azioni Contestuali Grafico")
168
  insights_chatbot_ui = gr.Chatbot(
169
  label="Chat Insights", type="messages", height=450,
 
199
  clicked_plot_config = next((p for p in plot_configs if p["id"] == plot_id_clicked), None)
200
  if not clicked_plot_config:
201
  logging.error(f"Config not found for plot_id {plot_id_clicked}")
 
202
  num_plots = len(plot_configs)
203
+ # 15 (base UI + states) + N (panels) + N (bomb_btns) + N (formula_btns) + N (explore_btns) + M (sections) = 15 + 4N + M
204
+ error_list_len = 15 + (4 * num_plots) + num_unique_sections
 
205
  error_list = [gr.update()] * error_list_len
206
  # Manually set key states to avoid further issues if possible
207
+ # Indices for states in action_panel_outputs_list:
208
+ # active_panel_action_state is at index 11
209
+ # current_chat_plot_id_st is at index 12
210
+ # chat_histories_st is at index 13
211
+ # explored_plot_id_state is at index 14
212
+ error_list[11] = current_active_action_from_state
213
+ error_list[12] = current_chat_plot_id
214
+ error_list[13] = current_chat_histories
215
+ error_list[14] = current_explored_plot_id
216
  return error_list
217
 
218
  clicked_plot_label = clicked_plot_config["label"]
 
220
  hypothetical_new_active_state = {"plot_id": plot_id_clicked, "type": action_type}
221
  is_toggling_off = current_active_action_from_state == hypothetical_new_active_state
222
 
223
+ # Initialize UI updates
224
  action_col_visible_update = gr.update(visible=False)
225
  insights_chatbot_visible_update, insights_chat_input_visible_update, insights_suggestions_row_visible_update = gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)
226
  formula_display_visible_update = gr.update(visible=False)
 
230
  new_active_action_state_to_set, new_current_chat_plot_id = None, current_chat_plot_id
231
  updated_chat_histories, new_explored_plot_id_to_set = current_chat_histories, current_explored_plot_id
232
 
233
+ # Lists for dynamic component updates
234
+ generated_panel_vis_updates = []
235
+ generated_bomb_btn_updates = []
236
+ generated_formula_btn_updates = []
237
+ generated_explore_btn_updates = []
238
  section_title_vis_updates = [gr.update()] * num_unique_sections
239
 
240
+
241
  if is_toggling_off:
242
+ new_active_action_state_to_set = None # Clear active state
243
+ action_col_visible_update = gr.update(visible=False) # Hide the right action column
244
  logging.info(f"Toggling OFF panel {action_type} for {plot_id_clicked}.")
245
+
246
+ # Reset bomb and formula buttons to their default icons
247
+ for _ in plot_configs:
248
+ generated_bomb_btn_updates.append(gr.update(value=BOMB_ICON))
249
+ generated_formula_btn_updates.append(gr.update(value=FORMULA_ICON))
250
+
251
  if current_explored_plot_id:
252
+ # If an explore view was active, restore visibility based on that
253
  explored_cfg = next((p for p in plot_configs if p["id"] == current_explored_plot_id), None)
254
  explored_sec = explored_cfg["section"] if explored_cfg else None
255
  for i, sec_name in enumerate(unique_ordered_sections):
256
  section_title_vis_updates[i] = gr.update(visible=(sec_name == explored_sec))
257
  for cfg in plot_configs:
258
  is_exp = (cfg["id"] == current_explored_plot_id)
259
+ generated_panel_vis_updates.append(gr.update(visible=is_exp))
260
+ generated_explore_btn_updates.append(gr.update(value=ACTIVE_ICON if is_exp else EXPLORE_ICON))
261
  else:
262
+ # No explore view, so all plots and sections become visible
263
  for i in range(num_unique_sections): section_title_vis_updates[i] = gr.update(visible=True)
264
  for _ in plot_configs:
265
+ generated_panel_vis_updates.append(gr.update(visible=True))
266
+ generated_explore_btn_updates.append(gr.update(value=EXPLORE_ICON)) # Reset explore buttons
267
+
268
+ if action_type == "insights": new_current_chat_plot_id = None # Clear chat context if insights panel is closed
 
269
 
270
+ else: # Toggling ON a new action or switching actions
271
  new_active_action_state_to_set = hypothetical_new_active_state
272
+ action_col_visible_update = gr.update(visible=True) # Show the right action column
273
+ new_explored_plot_id_to_set = None # Cancel any active explore view
274
+ logging.info(f"Toggling ON panel {action_type} for {plot_id_clicked}. Cancelling explore view if any.")
275
 
276
+ # Set visibility for sections and plot panels: only the clicked plot and its section title
277
  for i, sec_name in enumerate(unique_ordered_sections):
278
  section_title_vis_updates[i] = gr.update(visible=(sec_name == clicked_plot_section))
279
  for cfg in plot_configs:
280
+ generated_panel_vis_updates.append(gr.update(visible=(cfg["id"] == plot_id_clicked)))
281
+ generated_explore_btn_updates.append(gr.update(value=EXPLORE_ICON)) # Reset all explore buttons
282
+
283
+ # Update bomb and formula buttons: set active icon for the clicked one
284
  for cfg_btn in plot_configs:
285
  is_act_ins = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "insights"}
286
  is_act_for = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "formula"}
287
+ generated_bomb_btn_updates.append(gr.update(value=ACTIVE_ICON if is_act_ins else BOMB_ICON))
288
+ generated_formula_btn_updates.append(gr.update(value=ACTIVE_ICON if is_act_for else FORMULA_ICON))
289
+
290
  if action_type == "insights":
291
  insights_chatbot_visible_update, insights_chat_input_visible_update, insights_suggestions_row_visible_update = gr.update(visible=True), gr.update(visible=True), gr.update(visible=True)
292
  new_current_chat_plot_id = plot_id_clicked
293
  history = current_chat_histories.get(plot_id_clicked, [])
294
  summary = current_plot_data_for_chatbot.get(plot_id_clicked, f"No summary for '{clicked_plot_label}'.")
295
+ if not history: # First time opening chat for this plot
296
  prompt, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
297
  llm_hist = [{"role": "user", "content": prompt}]
298
+ # Simulate LLM call for Gradio streaming if needed, or direct call
299
  resp = await generate_llm_response(prompt, plot_id_clicked, clicked_plot_label, llm_hist, summary)
300
+ history = [{"role": "assistant", "content": resp}] # Start with AI's response
301
  updated_chat_histories = {**current_chat_histories, plot_id_clicked: history}
302
+ else: # Re-opening chat, just get suggestions
303
+ _, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary) # Get fresh suggestions
304
+
305
  chatbot_content_update = gr.update(value=history)
306
  s1_upd,s2_upd,s3_upd = gr.update(value=sugg[0] if sugg else "N/A"),gr.update(value=sugg[1] if len(sugg)>1 else "N/A"),gr.update(value=sugg[2] if len(sugg)>2 else "N/A")
307
+
308
  elif action_type == "formula":
309
  formula_display_visible_update = gr.update(visible=True)
310
  formula_close_hint_visible_update = gr.update(visible=True)
 
315
  f_text += f"### {f_data['title']}\n\n{f_data['description']}\n\n**Calculation:**\n" + "\n".join([f"- {s}" for s in f_data['calculation_steps']])
316
  else: f_text += "(No detailed formula information found.)"
317
  formula_content_update = gr.update(value=f_text)
318
+ new_current_chat_plot_id = None # Clear chat context if formula panel is opened
319
 
320
+ # Assemble the full list of updates in the correct order
321
  final_updates = [
322
  action_col_visible_update, insights_chatbot_visible_update, chatbot_content_update,
323
  insights_chat_input_visible_update, insights_suggestions_row_visible_update,
324
  s1_upd, s2_upd, s3_upd, formula_display_visible_update, formula_content_update,
325
+ formula_close_hint_md, # This is the component for the hint's visibility
326
+ # States
327
  new_active_action_state_to_set, new_current_chat_plot_id, updated_chat_histories,
328
  new_explored_plot_id_to_set
329
  ]
330
+ final_updates.extend(generated_panel_vis_updates) # N panel visibility updates
331
+ final_updates.extend(generated_bomb_btn_updates) # N bomb button updates
332
+ final_updates.extend(generated_formula_btn_updates) # N formula button updates
333
+ final_updates.extend(generated_explore_btn_updates) # N explore button updates
334
+ final_updates.extend(section_title_vis_updates) # M section title visibility updates
335
+
336
+ # Expected length: 11 (base UI) + 4 (states) + N (panels) + N (bomb_btns) + N (formula_btns) + N (explore_btns) + M (sections)
337
+ # = 15 + 4N + M. For N=19, M=6: 15 + 4*19 + 6 = 15 + 76 + 6 = 97.
338
+ logging.debug(f"handle_panel_action returning {len(final_updates)} updates. Expected 97 for N=19, M=6.")
339
  return final_updates
340
 
341
  async def handle_chat_message_submission(user_message: str, current_plot_id: str, chat_histories: dict, current_plot_data_for_chatbot: dict ):
342
  if not current_plot_id or not user_message.strip():
343
+ # Ensure history is a list for the chatbot component
344
+ current_history_for_plot = chat_histories.get(current_plot_id, [])
345
+ if not isinstance(current_history_for_plot, list): current_history_for_plot = []
346
+ yield current_history_for_plot, gr.update(value=""), chat_histories; return
347
+
348
  cfg = next((p for p in plot_configs if p["id"] == current_plot_id), None)
349
  lbl = cfg["label"] if cfg else "Selected Plot"
350
  summary = current_plot_data_for_chatbot.get(current_plot_id, f"No summary for '{lbl}'.")
351
+
352
+ # Ensure current history is a list
353
+ hist_for_plot = chat_histories.get(current_plot_id, [])
354
+ if not isinstance(hist_for_plot, list): hist_for_plot = [] # Safeguard
355
+
356
+ hist = hist_for_plot.copy() + [{"role": "user", "content": user_message}]
357
+ yield hist, gr.update(value=""), chat_histories # Show user message immediately
358
+
359
+ # Call LLM
360
  resp = await generate_llm_response(user_message, current_plot_id, lbl, hist, summary)
361
  hist.append({"role": "assistant", "content": resp})
362
+
363
+ # Update chat histories state
364
+ updated_chat_histories = {**chat_histories, current_plot_id: hist}
365
+ yield hist, "", updated_chat_histories
366
+
367
 
368
  async def handle_suggested_question_click(suggestion_text: str, current_plot_id: str, chat_histories: dict, current_plot_data_for_chatbot: dict):
369
+ if not current_plot_id or not suggestion_text.strip() or suggestion_text == "N/A":
370
+ current_history_for_plot = chat_histories.get(current_plot_id, [])
371
+ if not isinstance(current_history_for_plot, list): current_history_for_plot = []
372
+ yield current_history_for_plot, gr.update(value=""), chat_histories; return
373
+
374
+ # Use the generator to stream updates from handle_chat_message_submission
375
+ async for update_chunk in handle_chat_message_submission(suggestion_text, current_plot_id, chat_histories, current_plot_data_for_chatbot):
376
+ yield update_chunk
377
+
378
 
379
  def handle_explore_click(plot_id_clicked, current_explored_plot_id_from_state, current_active_panel_action_state):
380
  logging.info(f"Explore Click: Plot '{plot_id_clicked}'. Current Explored: {current_explored_plot_id_from_state}. Active Panel: {current_active_panel_action_state}")
381
  num_plots = len(plot_configs)
382
  if not plot_ui_objects:
383
  logging.error("plot_ui_objects not populated for handle_explore_click.")
384
+ # explore_outputs_list: 4 base states/UI + N panels + N explore_btns + N bomb_btns + N formula_btns + M sections
385
+ error_list_len = 4 + (4 * num_plots) + num_unique_sections
 
386
  error_list = [gr.update()] * error_list_len
387
+ error_list[0] = current_explored_plot_id_from_state # explored_plot_id_state
388
+ error_list[2] = current_active_panel_action_state # active_panel_action_state
389
  return error_list
390
 
391
+ new_explored_id_to_set = None
392
+ is_toggling_off_explore = (plot_id_clicked == current_explored_plot_id_from_state)
393
 
394
+ action_col_upd = gr.update() # Default to no change for action column visibility
395
+ new_active_panel_state_upd = current_active_panel_action_state # Default to no change
396
+ formula_hint_upd = gr.update(visible=False) # Hide formula hint by default
397
+
398
+ panel_vis_updates = []
399
+ explore_btns_updates = []
400
+ bomb_btns_updates = []
401
+ formula_btns_updates = []
402
+ section_title_vis_updates = [gr.update()] * num_unique_sections
403
 
404
  clicked_cfg = next((p for p in plot_configs if p["id"] == plot_id_clicked), None)
405
  sec_of_clicked = clicked_cfg["section"] if clicked_cfg else None
406
 
407
  if is_toggling_off_explore:
408
+ new_explored_id_to_set = None # Clear explored state
409
+ logging.info(f"Stopping explore for {plot_id_clicked}. All plots/sections to be visible.")
410
+ for i in range(num_unique_sections): section_title_vis_updates[i] = gr.update(visible=True)
411
  for _ in plot_configs:
412
+ panel_vis_updates.append(gr.update(visible=True))
413
+ explore_btns_updates.append(gr.update(value=EXPLORE_ICON))
414
+ # If an action panel was active, its buttons are handled by its own logic if it's closed.
415
+ # Here, we just ensure explore buttons are reset. Bomb/Formula buttons remain as they were unless an active panel is closed.
416
+ bomb_btns_updates.append(gr.update()) # No change unless active panel is closed
417
+ formula_btns_updates.append(gr.update()) # No change
418
+ else: # Toggling ON explore for a plot
419
  new_explored_id_to_set = plot_id_clicked
420
  logging.info(f"Exploring {plot_id_clicked}. Hiding other plots/sections.")
421
+ for i, sec_name in enumerate(unique_ordered_sections):
422
+ section_title_vis_updates[i] = gr.update(visible=(sec_name == sec_of_clicked))
423
+ for cfg in plot_configs:
424
  is_target = (cfg["id"] == new_explored_id_to_set)
425
+ panel_vis_updates.append(gr.update(visible=is_target))
426
+ explore_btns_updates.append(gr.update(value=ACTIVE_ICON if is_target else EXPLORE_ICON))
427
+
428
+ # If an action panel (insights/formula) is currently active, close it
429
+ if current_active_panel_action_state:
 
 
 
430
  logging.info("Closing active insight/formula panel due to explore click.")
431
+ action_col_upd = gr.update(visible=False) # Hide the right action column
432
+ new_active_panel_state_upd = None # Clear the active panel state
433
+ formula_hint_upd = gr.update(visible=False) # Hide formula hint
434
+ # Reset all bomb and formula buttons to default icons
435
+ for _ in plot_configs:
436
+ bomb_btns_updates.append(gr.update(value=BOMB_ICON))
437
+ formula_btns_updates.append(gr.update(value=FORMULA_ICON))
438
+ else:
439
+ # No action panel was active, bomb/formula buttons remain as they are (no update)
440
+ for _ in plot_configs:
441
+ bomb_btns_updates.append(gr.update())
442
+ formula_btns_updates.append(gr.update())
443
+
444
+ # Order: states/UI (4), panels (N), explore_btns (N), bomb_btns (N), formula_btns (N), section_titles (M)
445
+ # Total: 4 + 4N + M.
446
+ final_explore_updates = [
447
+ new_explored_id_to_set, action_col_upd, new_active_panel_state_upd, formula_hint_upd
448
+ ]
449
+ final_explore_updates.extend(panel_vis_updates)
450
+ final_explore_updates.extend(explore_btns_updates)
451
+ final_explore_updates.extend(bomb_btns_updates)
452
+ final_explore_updates.extend(formula_btns_updates)
453
+ final_explore_updates.extend(section_title_vis_updates)
454
 
455
+ logging.debug(f"handle_explore_click returning {len(final_explore_updates)} updates. Expected {4 + 4*len(plot_configs) + num_unique_sections}.")
456
+ return final_explore_updates
457
+
 
458
 
459
  # --- Define Output Lists for Event Handlers ---
460
  # Base UI for action panel (insights/formula): 11 elements
461
  _base_action_panel_ui_outputs = [
462
+ global_actions_column_ui, insights_chatbot_ui, insights_chatbot_ui, # Chatbot vis + value
463
  insights_chat_input_ui, insights_suggestions_row_ui,
464
  insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn,
465
  formula_display_markdown_ui, formula_display_markdown_ui, # Formula MD (vis + value)
466
+ formula_close_hint_md # Formula hint visibility
467
  ]
468
  # States for action panel: 4 elements
469
  _action_panel_state_outputs = [active_panel_action_state, current_chat_plot_id_st, chat_histories_st, explored_plot_id_state]
 
474
  action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs]) # +N (formula_button)
475
  action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("explore_button", gr.update()) for pc in plot_configs]) # +N (explore_button)
476
  action_panel_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections]) # +M (sections)
477
+ # Total for action_panel_outputs_list = 15 + 4N + M. For N=19, M=6: 15 + 76 + 6 = 97. Correct.
478
 
479
  # Base UI/States for explore click: 4 elements
480
  _explore_base_outputs = [explored_plot_id_state, global_actions_column_ui, active_panel_action_state, formula_close_hint_md]
 
484
  explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("bomb_button", gr.update()) for pc in plot_configs]) # +N (bomb_button)
485
  explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs]) # +N (formula_button)
486
  explore_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections]) # +M (sections)
487
+ # Total for explore_outputs_list = 4 + 4N + M. For N=19, M=6: 4 + 76 + 6 = 86. Correct.
488
 
489
  action_click_inputs = [active_panel_action_state, chat_histories_st, current_chat_plot_id_st, plot_data_for_chatbot_st, explored_plot_id_state]
490
  explore_click_inputs = [explored_plot_id_state, active_panel_action_state]
 
498
  plot_id = config_item["id"]
499
  if plot_id in plot_ui_objects:
500
  ui_obj = plot_ui_objects[plot_id]
501
+ # Ensure buttons exist before attaching handlers
502
+ if ui_obj.get("bomb_button"):
503
+ ui_obj["bomb_button"].click(fn=create_panel_action_handler(plot_id, "insights"), inputs=action_click_inputs, outputs=action_panel_outputs_list, api_name=f"action_insights_{plot_id}")
504
+ if ui_obj.get("formula_button"):
505
+ ui_obj["formula_button"].click(fn=create_panel_action_handler(plot_id, "formula"), inputs=action_click_inputs, outputs=action_panel_outputs_list, api_name=f"action_formula_{plot_id}")
506
+ if ui_obj.get("explore_button"):
507
+ ui_obj["explore_button"].click(fn=lambda current_explored_val, current_active_panel_val, p_id=plot_id: handle_explore_click(p_id, current_explored_val, current_active_panel_val), inputs=explore_click_inputs, outputs=explore_outputs_list, api_name=f"action_explore_{plot_id}")
508
  else: logging.warning(f"UI object for plot_id '{plot_id}' not found for click handlers.")
509
 
510
+
511
  chat_submission_outputs = [insights_chatbot_ui, insights_chat_input_ui, chat_histories_st]
512
  chat_submission_inputs = [insights_chat_input_ui, current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
513
  insights_chat_input_ui.submit(fn=handle_chat_message_submission, inputs=chat_submission_inputs, outputs=chat_submission_outputs, api_name="submit_chat_message")
514
+
515
+ suggestion_click_inputs_base = [current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
516
+ insights_suggestion_1_btn.click(fn=handle_suggested_question_click, inputs=[insights_suggestion_1_btn] + suggestion_click_inputs_base, outputs=chat_submission_outputs, api_name="click_suggestion_1")
517
+ insights_suggestion_2_btn.click(fn=handle_suggested_question_click, inputs=[insights_suggestion_2_btn] + suggestion_click_inputs_base, outputs=chat_submission_outputs, api_name="click_suggestion_2")
518
+ insights_suggestion_3_btn.click(fn=handle_suggested_question_click, inputs=[insights_suggestion_3_btn] + suggestion_click_inputs_base, outputs=chat_submission_outputs, api_name="click_suggestion_3")
519
+
520
 
521
  def refresh_all_analytics_ui_elements(current_token_state, date_filter_val, custom_start_val, custom_end_val, current_chat_histories):
522
  logging.info("Refreshing all analytics UI elements and resetting actions/chat.")
523
  plot_gen_results = update_analytics_plots_figures(current_token_state, date_filter_val, custom_start_val, custom_end_val, plot_configs)
524
  status_msg, gen_figs, new_summaries = plot_gen_results[0], plot_gen_results[1:-1], plot_gen_results[-1]
525
 
526
+ all_updates = [status_msg] # 1. analytics_status_md
527
+ all_updates.extend(gen_figs if len(gen_figs) == len(plot_configs) else [create_placeholder_plot("Error", f"Fig missing {i}") for i in range(len(plot_configs))]) # +N (plot figures)
528
 
529
+ # UI Resets for action panel (9 elements)
530
  all_updates.extend([
531
+ gr.update(visible=False), # global_actions_column_ui
532
+ gr.update(value=[], visible=False), # insights_chatbot_ui (value + visibility)
533
+ gr.update(value="", visible=False), # insights_chat_input_ui
534
+ gr.update(visible=False), # insights_suggestions_row_ui
535
+ gr.update(value="S1"), gr.update(value="S2"), gr.update(value="S3"), # sugg_btns
536
+ gr.update(value="Formula details here.", visible=False), # formula_display_markdown_ui (value + visibility)
537
+ gr.update(visible=False) # formula_close_hint_md
538
+ ])
539
 
540
+ # State Resets (4 elements)
541
+ all_updates.extend([
542
+ None, # active_panel_action_state
543
+ None, # current_chat_plot_id_st
544
+ {}, # chat_histories_st (reset all chat histories)
545
+ new_summaries # plot_data_for_chatbot_st
546
+ ])
547
+
548
+ # Plot-specific buttons (3 per plot: bomb, formula, explore) and panel visibility (1 per plot)
549
+ for _ in plot_configs:
550
+ all_updates.extend([
551
+ gr.update(value=BOMB_ICON),
552
+ gr.update(value=FORMULA_ICON),
553
+ gr.update(value=EXPLORE_ICON),
554
+ gr.update(visible=True) # panel_component visibility
555
+ ])
556
 
557
  all_updates.append(None) # Reset explored_plot_id_state (1 element)
558
+ all_updates.extend([gr.update(visible=True)] * num_unique_sections) # Make all section titles visible (M elements)
559
+
560
+ # Total: 1 (status) + N (figs) + 9 (UI resets) + 4 (state resets) + 4N (plot buttons/panels) + 1 (explored_state) + M (sections)
561
+ # = 1 + N + 9 + 4 + 4N + 1 + M = 15 + 5N + M.
562
+ # For N=19, M=6: 15 + 5*19 + 6 = 15 + 95 + 6 = 116. This matches apply_filter_and_sync_outputs_list.
563
+ logging.info(f"Prepared {len(all_updates)} updates for analytics refresh. Expected {15 + 5*len(plot_configs) + num_unique_sections}.")
564
  return all_updates
565
 
566
  # Construction of apply_filter_and_sync_outputs_list
 
 
 
567
  apply_filter_and_sync_outputs_list = [analytics_status_md] # 1. Status
568
  apply_filter_and_sync_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("plot_component", gr.update()) for pc in plot_configs]) # 2. Plot figures (N)
569
 
570
+ # 3. UI Resets for action panel (9 elements)
571
  _ui_resets_for_filter = [
572
  global_actions_column_ui, insights_chatbot_ui, insights_chat_input_ui,
573
  insights_suggestions_row_ui, insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn,
 
586
  plot_ui_objects.get(pid, {}).get("bomb_button", gr.update()),
587
  plot_ui_objects.get(pid, {}).get("formula_button", gr.update()),
588
  plot_ui_objects.get(pid, {}).get("explore_button", gr.update()),
589
+ plot_ui_objects.get(pid, {}).get("panel_component", gr.update()) # Panel visibility
590
  ])
591
 
592
  # 6. Explored state reset (1 element)
 
594
 
595
  # 7. Section Titles (M elements)
596
  apply_filter_and_sync_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections])
597
+ # Total elements: 1 + N + 9 + 4 + 4N + 1 + M = 15 + 5N + M. Correct.
 
598
 
599
  apply_filter_btn.click(
600
  fn=refresh_all_analytics_ui_elements,
 
632
  sync_event_part3 = sync_event_part2.then(fn=display_main_dashboard, inputs=[token_state], outputs=[dashboard_display_html], show_progress=False)
633
  sync_event_final = sync_event_part3.then(fn=refresh_all_analytics_ui_elements, inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st], outputs=apply_filter_and_sync_outputs_list, show_progress="full")
634
 
635
+
636
  if __name__ == "__main__":
637
  if not os.environ.get(LINKEDIN_CLIENT_ID_ENV_VAR): logging.warning(f"ATTENZIONE: '{LINKEDIN_CLIENT_ID_ENV_VAR}' non impostata.")
638
  if not all(os.environ.get(var) for var in [BUBBLE_APP_NAME_ENV_VAR, BUBBLE_API_KEY_PRIVATE_ENV_VAR, BUBBLE_API_ENDPOINT_ENV_VAR]):
639
  logging.warning("ATTENZIONE: Variabili Bubble non impostate.")
640
  try: logging.info(f"Matplotlib: {matplotlib.__version__}, Backend: {matplotlib.get_backend()}")
641
  except ImportError: logging.warning("Matplotlib non trovato.")
642
+ app.launch(server_name="0.0.0.0", server_port=7860, debug=True)