GuglielmoTor commited on
Commit
1644cc1
·
verified ·
1 Parent(s): 110f4e5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +268 -505
app.py CHANGED
@@ -58,17 +58,16 @@ except ImportError as e:
58
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
59
 
60
  # 1. Set Vertex AI usage preference (if applicable)
61
- os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "False"
62
 
63
  # 2. Get your API key from your chosen environment variable name
64
- # Replace "YOUR_GEMINI_API_KEY_ENV_NAME" with the actual name you use, e.g., "GEMINI_API_KEY" or "GOOGLE_API_KEY"
65
- user_provided_api_key = os.environ.get("GEMINI_API_KEY")
66
 
67
  if user_provided_api_key:
68
  os.environ["GOOGLE_API_KEY"] = user_provided_api_key
69
- logging.info("GOOGLE_API_KEY environment variable has been set from YOUR_GEMINI_API_KEY_ENV_NAME.")
70
  else:
71
- logging.error(f"CRITICAL ERROR: The API key environment variable 'YOUR_GEMINI_API_KEY_ENV_NAME' was not found. The application may not function correctly.")
72
 
73
 
74
  # --- Gradio UI Blocks ---
@@ -91,9 +90,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
91
 
92
  # --- NEW STATES FOR AGENTIC PIPELINE ---
93
  orchestration_raw_results_st = gr.State(None)
94
- # Stores list of dicts: {'okr_index', 'kr_index', 'okr_objective', 'kr_description', 'unique_kr_id'}
95
  key_results_for_selection_st = gr.State([])
96
- # Stores list of unique_kr_id strings that are selected by the user
97
  selected_key_result_ids_st = gr.State([])
98
 
99
 
@@ -107,10 +104,6 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
107
  def initial_load_sequence(url_token, org_urn_val, current_state):
108
  status_msg, new_state, btn_update = process_and_store_bubble_token(url_token, org_urn_val, current_state)
109
  dashboard_content = display_main_dashboard(new_state)
110
- # Initial call to populate analytics plots with default "Sempre"
111
- # This will now also trigger the agentic pipeline for the first time.
112
- # However, the outputs for agentic tabs are handled by apply_filter_btn.click and sync.then
113
- # So, we don't need to return agentic UI updates from here directly.
114
  return status_msg, new_state, btn_update, dashboard_content
115
 
116
  with gr.Tabs() as tabs:
@@ -120,28 +113,25 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
120
  sync_status_html_output = gr.HTML("<p style='text-align:center;'>Stato sincronizzazione...</p>")
121
  dashboard_display_html = gr.HTML("<p style='text-align:center;'>Caricamento dashboard...</p>")
122
 
123
- org_urn_display.change(
124
- fn=initial_load_sequence,
125
- inputs=[url_user_token_display, org_urn_display, token_state],
126
- outputs=[status_box, token_state, sync_data_btn, dashboard_display_html],
127
- show_progress="full"
128
- )
129
 
130
  with gr.TabItem("2️⃣ Analisi", id="tab_analytics"):
131
  gr.Markdown("## 📈 Analisi Performance LinkedIn")
132
- gr.Markdown("Seleziona un intervallo di date. Clicca i pulsanti (💣 Insights, ƒ Formula, 🧭 Esplora) su un grafico per azioni.")
133
- analytics_status_md = gr.Markdown("Stato analisi...")
 
134
 
135
  with gr.Row():
136
  date_filter_selector = gr.Radio(
137
  ["Sempre", "Ultimi 7 Giorni", "Ultimi 30 Giorni", "Intervallo Personalizzato"],
138
- label="Seleziona Intervallo Date", value="Sempre", scale=3
139
  )
140
  with gr.Column(scale=2):
141
  custom_start_date_picker = gr.DateTime(label="Data Inizio", visible=False, include_time=False, type="datetime")
142
  custom_end_date_picker = gr.DateTime(label="Data Fine", visible=False, include_time=False, type="datetime")
143
 
144
- apply_filter_btn = gr.Button("🔍 Applica Filtro & Aggiorna Analisi", variant="primary")
145
 
146
  def toggle_custom_date_pickers(selection):
147
  is_custom = selection == "Intervallo Personalizzato"
@@ -179,11 +169,11 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
179
  unique_ordered_sections = list(OrderedDict.fromkeys(pc["section"] for pc in plot_configs))
180
  num_unique_sections = len(unique_ordered_sections)
181
 
182
- active_panel_action_state = gr.State(None)
183
- explored_plot_id_state = gr.State(None)
184
 
185
- plot_ui_objects = {}
186
- section_titles_map = {}
187
 
188
  with gr.Row(equal_height=False):
189
  with gr.Column(scale=8) as plots_area_col:
@@ -194,15 +184,15 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
194
  logging.error("section_titles_map from build_analytics_tab_plot_area is incomplete.")
195
  for sec_name in unique_ordered_sections:
196
  if sec_name not in section_titles_map:
197
- section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)")
198
  else:
199
  logging.error("build_analytics_tab_plot_area did not return a tuple of (plot_ui_objects, section_titles_map).")
200
- plot_ui_objects = ui_elements_tuple if isinstance(ui_elements_tuple, dict) else {}
201
- for sec_name in unique_ordered_sections:
202
  section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)")
203
 
204
 
205
- with gr.Column(scale=4, visible=False) as global_actions_column_ui:
206
  gr.Markdown("### 💡 Azioni Contestuali Grafico")
207
  insights_chatbot_ui = gr.Chatbot(
208
  label="Chat Insights", type="messages", height=450,
@@ -221,140 +211,82 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
221
  formula_display_markdown_ui = gr.Markdown(
222
  "I dettagli sulla formula/metodologia appariranno qui.", visible=False
223
  )
224
- formula_close_hint_md = gr.Markdown( # Component for the hint's visibility
225
- "<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>",
226
  visible=False
227
  )
228
 
229
- # --- ASYNC HANDLERS FOR ANALYTICS TAB ---
230
  async def handle_panel_action(
231
  plot_id_clicked: str, action_type: str, current_active_action_from_state: dict,
232
  current_chat_histories: dict, current_chat_plot_id: str,
233
- current_plot_data_for_chatbot: dict, current_explored_plot_id: str
234
  ):
235
  logging.info(f"Panel Action: '{action_type}' for plot '{plot_id_clicked}'. Active: {current_active_action_from_state}, Explored: {current_explored_plot_id}")
236
-
237
  clicked_plot_config = next((p for p in plot_configs if p["id"] == plot_id_clicked), None)
238
  if not clicked_plot_config:
239
  logging.error(f"Config not found for plot_id {plot_id_clicked}")
240
  num_plots = len(plot_configs)
241
- error_list_len = 15 + (4 * num_plots) + num_unique_sections
242
  error_list = [gr.update()] * error_list_len
243
- error_list[11] = current_active_action_from_state
244
- error_list[12] = current_chat_plot_id
245
- error_list[13] = current_chat_histories
246
- error_list[14] = current_explored_plot_id
247
  return error_list
248
-
249
- clicked_plot_label = clicked_plot_config["label"]
250
- clicked_plot_section = clicked_plot_config["section"]
251
  hypothetical_new_active_state = {"plot_id": plot_id_clicked, "type": action_type}
252
  is_toggling_off = current_active_action_from_state == hypothetical_new_active_state
253
-
254
  action_col_visible_update = gr.update(visible=False)
255
  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)
256
- formula_display_visible_update = gr.update(visible=False)
257
- formula_close_hint_visible_update = gr.update(visible=False)
258
  chatbot_content_update, s1_upd, s2_upd, s3_upd, formula_content_update = gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
259
-
260
  new_active_action_state_to_set, new_current_chat_plot_id = None, current_chat_plot_id
261
  updated_chat_histories, new_explored_plot_id_to_set = current_chat_histories, current_explored_plot_id
262
-
263
- generated_panel_vis_updates = []
264
- generated_bomb_btn_updates = []
265
- generated_formula_btn_updates = []
266
- generated_explore_btn_updates = []
267
  section_title_vis_updates = [gr.update()] * num_unique_sections
268
-
269
-
270
  if is_toggling_off:
271
- new_active_action_state_to_set = None
272
- action_col_visible_update = gr.update(visible=False)
273
  logging.info(f"Toggling OFF panel {action_type} for {plot_id_clicked}.")
274
-
275
- for _ in plot_configs:
276
- generated_bomb_btn_updates.append(gr.update(value=BOMB_ICON))
277
- generated_formula_btn_updates.append(gr.update(value=FORMULA_ICON))
278
-
279
- if current_explored_plot_id:
280
  explored_cfg = next((p for p in plot_configs if p["id"] == current_explored_plot_id), None)
281
  explored_sec = explored_cfg["section"] if explored_cfg else None
282
- for i, sec_name in enumerate(unique_ordered_sections):
283
- section_title_vis_updates[i] = gr.update(visible=(sec_name == explored_sec))
284
- for cfg in plot_configs:
285
- is_exp = (cfg["id"] == current_explored_plot_id)
286
- generated_panel_vis_updates.append(gr.update(visible=is_exp))
287
- generated_explore_btn_updates.append(gr.update(value=ACTIVE_ICON if is_exp else EXPLORE_ICON))
288
- else:
289
  for i in range(num_unique_sections): section_title_vis_updates[i] = gr.update(visible=True)
290
- for _ in plot_configs:
291
- generated_panel_vis_updates.append(gr.update(visible=True))
292
- generated_explore_btn_updates.append(gr.update(value=EXPLORE_ICON))
293
-
294
  if action_type == "insights": new_current_chat_plot_id = None
295
-
296
- else: # Toggling ON a new action or switching actions
297
- new_active_action_state_to_set = hypothetical_new_active_state
298
- action_col_visible_update = gr.update(visible=True)
299
- new_explored_plot_id_to_set = None
300
  logging.info(f"Toggling ON panel {action_type} for {plot_id_clicked}. Cancelling explore view if any.")
301
-
302
- for i, sec_name in enumerate(unique_ordered_sections):
303
- section_title_vis_updates[i] = gr.update(visible=(sec_name == clicked_plot_section))
304
- for cfg in plot_configs:
305
- generated_panel_vis_updates.append(gr.update(visible=(cfg["id"] == plot_id_clicked)))
306
- generated_explore_btn_updates.append(gr.update(value=EXPLORE_ICON))
307
-
308
- for cfg_btn in plot_configs:
309
  is_act_ins = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "insights"}
310
  is_act_for = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "formula"}
311
- generated_bomb_btn_updates.append(gr.update(value=ACTIVE_ICON if is_act_ins else BOMB_ICON))
312
- generated_formula_btn_updates.append(gr.update(value=ACTIVE_ICON if is_act_for else FORMULA_ICON))
313
-
314
  if action_type == "insights":
315
  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)
316
  new_current_chat_plot_id = plot_id_clicked
317
  history = current_chat_histories.get(plot_id_clicked, [])
318
  summary = current_plot_data_for_chatbot.get(plot_id_clicked, f"No summary for '{clicked_plot_label}'.")
319
- if not history:
320
  prompt, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
321
  llm_hist = [{"role": "user", "content": prompt}]
322
- resp = await generate_llm_response(prompt, plot_id_clicked, clicked_plot_label, llm_hist, summary) # This is your existing LLM call
323
- history = [{"role": "assistant", "content": resp}]
324
- updated_chat_histories = {**current_chat_histories, plot_id_clicked: history}
325
- else:
326
- _, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
327
-
328
  chatbot_content_update = gr.update(value=history)
329
  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")
330
-
331
  elif action_type == "formula":
332
- formula_display_visible_update = gr.update(visible=True)
333
- formula_close_hint_visible_update = gr.update(visible=True)
334
  f_key = PLOT_ID_TO_FORMULA_KEY_MAP.get(plot_id_clicked)
335
  f_text = f"**Formula/Methodology for: {clicked_plot_label}** (ID: `{plot_id_clicked}`)\n\n"
336
- if f_key and f_key in PLOT_FORMULAS:
337
- f_data = PLOT_FORMULAS[f_key]
338
- 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']])
339
  else: f_text += "(No detailed formula information found.)"
340
- formula_content_update = gr.update(value=f_text)
341
- new_current_chat_plot_id = None
342
-
343
- final_updates = [
344
- action_col_visible_update, insights_chatbot_visible_update, chatbot_content_update,
345
- insights_chat_input_visible_update, insights_suggestions_row_visible_update,
346
- s1_upd, s2_upd, s3_upd, formula_display_visible_update, formula_content_update,
347
- formula_close_hint_visible_update, # Corrected from formula_close_hint_md
348
- new_active_action_state_to_set, new_current_chat_plot_id, updated_chat_histories,
349
- new_explored_plot_id_to_set
350
- ]
351
-
352
- final_updates.extend(generated_panel_vis_updates)
353
- final_updates.extend(generated_bomb_btn_updates)
354
- final_updates.extend(generated_formula_btn_updates)
355
- final_updates.extend(generated_explore_btn_updates)
356
- final_updates.extend(section_title_vis_updates)
357
-
358
  logging.debug(f"handle_panel_action returning {len(final_updates)} updates. Expected {15 + 4*len(plot_configs) + num_unique_sections}.")
359
  return final_updates
360
 
@@ -363,160 +295,84 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
363
  current_history_for_plot = chat_histories.get(current_plot_id, [])
364
  if not isinstance(current_history_for_plot, list): current_history_for_plot = []
365
  yield current_history_for_plot, gr.update(value=""), chat_histories; return
366
-
367
  cfg = next((p for p in plot_configs if p["id"] == current_plot_id), None)
368
  lbl = cfg["label"] if cfg else "Selected Plot"
369
  summary = current_plot_data_for_chatbot.get(current_plot_id, f"No summary for '{lbl}'.")
370
-
371
  hist_for_plot = chat_histories.get(current_plot_id, [])
372
- if not isinstance(hist_for_plot, list): hist_for_plot = []
373
-
374
  hist = hist_for_plot.copy() + [{"role": "user", "content": user_message}]
375
- yield hist, gr.update(value=""), chat_histories
376
-
377
- resp = await generate_llm_response(user_message, current_plot_id, lbl, hist, summary) # Existing LLM
378
  hist.append({"role": "assistant", "content": resp})
379
-
380
  updated_chat_histories = {**chat_histories, current_plot_id: hist}
381
  yield hist, "", updated_chat_histories
382
 
383
-
384
  async def handle_suggested_question_click(suggestion_text: str, current_plot_id: str, chat_histories: dict, current_plot_data_for_chatbot: dict):
385
  if not current_plot_id or not suggestion_text.strip() or suggestion_text == "N/A":
386
  current_history_for_plot = chat_histories.get(current_plot_id, [])
387
  if not isinstance(current_history_for_plot, list): current_history_for_plot = []
388
  yield current_history_for_plot, gr.update(value=""), chat_histories; return
389
-
390
  async for update_chunk in handle_chat_message_submission(suggestion_text, current_plot_id, chat_histories, current_plot_data_for_chatbot):
391
  yield update_chunk
392
 
393
-
394
  def handle_explore_click(plot_id_clicked, current_explored_plot_id_from_state, current_active_panel_action_state):
395
- # This function remains synchronous as per original
396
  logging.info(f"Explore Click: Plot '{plot_id_clicked}'. Current Explored: {current_explored_plot_id_from_state}. Active Panel: {current_active_panel_action_state}")
397
  num_plots = len(plot_configs)
398
- if not plot_ui_objects:
399
  logging.error("plot_ui_objects not populated for handle_explore_click.")
400
- error_list_len = 4 + (4 * num_plots) + num_unique_sections
401
- error_list = [gr.update()] * error_list_len
402
- error_list[0] = current_explored_plot_id_from_state
403
- error_list[2] = current_active_panel_action_state
404
  return error_list
405
-
406
  new_explored_id_to_set = None
407
  is_toggling_off_explore = (plot_id_clicked == current_explored_plot_id_from_state)
408
-
409
- action_col_upd = gr.update()
410
- new_active_panel_state_upd = current_active_panel_action_state
411
- formula_hint_upd = gr.update(visible=False)
412
-
413
- panel_vis_updates = []
414
- explore_btns_updates = []
415
- bomb_btns_updates = []
416
- formula_btns_updates = []
417
  section_title_vis_updates = [gr.update()] * num_unique_sections
418
-
419
  clicked_cfg = next((p for p in plot_configs if p["id"] == plot_id_clicked), None)
420
  sec_of_clicked = clicked_cfg["section"] if clicked_cfg else None
421
-
422
  if is_toggling_off_explore:
423
- new_explored_id_to_set = None
424
  logging.info(f"Stopping explore for {plot_id_clicked}. All plots/sections to be visible.")
425
  for i in range(num_unique_sections): section_title_vis_updates[i] = gr.update(visible=True)
426
- for _ in plot_configs:
427
- panel_vis_updates.append(gr.update(visible=True))
428
- explore_btns_updates.append(gr.update(value=EXPLORE_ICON))
429
- bomb_btns_updates.append(gr.update())
430
- formula_btns_updates.append(gr.update())
431
- else:
432
  new_explored_id_to_set = plot_id_clicked
433
  logging.info(f"Exploring {plot_id_clicked}. Hiding other plots/sections.")
434
- for i, sec_name in enumerate(unique_ordered_sections):
435
- section_title_vis_updates[i] = gr.update(visible=(sec_name == sec_of_clicked))
436
- for cfg in plot_configs:
437
- is_target = (cfg["id"] == new_explored_id_to_set)
438
- panel_vis_updates.append(gr.update(visible=is_target))
439
- explore_btns_updates.append(gr.update(value=ACTIVE_ICON if is_target else EXPLORE_ICON))
440
-
441
  if current_active_panel_action_state:
442
  logging.info("Closing active insight/formula panel due to explore click.")
443
- action_col_upd = gr.update(visible=False)
444
- new_active_panel_state_upd = None
445
- formula_hint_upd = gr.update(visible=False)
446
- # Reset bomb and formula buttons to non-active
447
- bomb_btns_updates = [gr.update(value=BOMB_ICON) for _ in plot_configs]
448
- formula_btns_updates = [gr.update(value=FORMULA_ICON) for _ in plot_configs]
449
- else: # No active panel, so no need to reset bomb/formula buttons beyond what explore does
450
- bomb_btns_updates = [gr.update() for _ in plot_configs]
451
- formula_btns_updates = [gr.update() for _ in plot_configs]
452
-
453
- final_explore_updates = [
454
- new_explored_id_to_set, action_col_upd, new_active_panel_state_upd, formula_hint_upd
455
- ]
456
- final_explore_updates.extend(panel_vis_updates)
457
- final_explore_updates.extend(explore_btns_updates)
458
- final_explore_updates.extend(bomb_btns_updates)
459
- final_explore_updates.extend(formula_btns_updates)
460
- final_explore_updates.extend(section_title_vis_updates)
461
-
462
  logging.debug(f"handle_explore_click returning {len(final_explore_updates)} updates. Expected {4 + 4*len(plot_configs) + num_unique_sections}.")
463
  return final_explore_updates
464
 
465
-
466
- _base_action_panel_ui_outputs = [
467
- global_actions_column_ui, insights_chatbot_ui, insights_chatbot_ui,
468
- insights_chat_input_ui, insights_suggestions_row_ui,
469
- insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn,
470
- formula_display_markdown_ui, formula_display_markdown_ui,
471
- formula_close_hint_md
472
- ]
473
  _action_panel_state_outputs = [active_panel_action_state, current_chat_plot_id_st, chat_histories_st, explored_plot_id_state]
474
-
475
- action_panel_outputs_list = _base_action_panel_ui_outputs + _action_panel_state_outputs
476
- action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("panel_component", gr.update()) for pc in plot_configs])
477
- action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("bomb_button", gr.update()) for pc in plot_configs])
478
- action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs])
479
- action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("explore_button", gr.update()) for pc in plot_configs])
480
- action_panel_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections])
481
-
482
- _explore_base_outputs = [explored_plot_id_state, global_actions_column_ui, active_panel_action_state, formula_close_hint_md]
483
- explore_outputs_list = _explore_base_outputs
484
- explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("panel_component", gr.update()) for pc in plot_configs])
485
- explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("explore_button", gr.update()) for pc in plot_configs])
486
- explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("bomb_button", gr.update()) for pc in plot_configs])
487
- explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs])
488
- explore_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections])
489
-
490
  action_click_inputs = [active_panel_action_state, chat_histories_st, current_chat_plot_id_st, plot_data_for_chatbot_st, explored_plot_id_state]
491
  explore_click_inputs = [explored_plot_id_state, active_panel_action_state]
492
-
493
  def create_panel_action_handler(p_id, action_type_str):
494
- async def _handler(curr_active_val, curr_chats_val, curr_chat_pid, curr_plot_data, curr_explored_id):
495
- return await handle_panel_action(p_id, action_type_str, curr_active_val, curr_chats_val, curr_chat_pid, curr_plot_data, curr_explored_id)
496
  return _handler
497
-
498
  for config_item in plot_configs:
499
  plot_id = config_item["id"]
500
  if plot_id in plot_ui_objects:
501
  ui_obj = plot_ui_objects[plot_id]
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(
508
- 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),
509
- inputs=explore_click_inputs,
510
- outputs=explore_outputs_list,
511
- api_name=f"action_explore_{plot_id}"
512
- )
513
  else: logging.warning(f"UI object for plot_id '{plot_id}' not found for click handlers.")
514
-
515
-
516
  chat_submission_outputs = [insights_chatbot_ui, insights_chat_input_ui, chat_histories_st]
517
  chat_submission_inputs = [insights_chat_input_ui, current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
518
  insights_chat_input_ui.submit(fn=handle_chat_message_submission, inputs=chat_submission_inputs, outputs=chat_submission_outputs, api_name="submit_chat_message")
519
-
520
  suggestion_click_inputs_base = [current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
521
  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")
522
  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")
@@ -526,313 +382,225 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
526
  refresh_mentions_display_btn = gr.Button("🔄 Aggiorna Visualizzazione Menzioni", variant="secondary")
527
  mentions_html = gr.HTML("Dati menzioni...")
528
  mentions_sentiment_dist_plot = gr.Plot(label="Distribuzione Sentiment Menzioni")
529
- refresh_mentions_display_btn.click(
530
- fn=run_mentions_tab_display, inputs=[token_state],
531
- outputs=[mentions_html, mentions_sentiment_dist_plot],
532
- show_progress="full"
533
- )
534
 
535
  with gr.TabItem("4️⃣ Statistiche Follower", id="tab_follower_stats"):
536
  refresh_follower_stats_btn = gr.Button("🔄 Aggiorna Visualizzazione Statistiche Follower", variant="secondary")
537
  follower_stats_html = gr.HTML("Statistiche follower...")
538
- with gr.Row():
539
- fs_plot_monthly_gains = gr.Plot(label="Guadagni Mensili Follower")
540
- with gr.Row():
541
- fs_plot_seniority = gr.Plot(label="Follower per Anzianità (Top 10 Organici)")
542
- fs_plot_industry = gr.Plot(label="Follower per Settore (Top 10 Organici)")
543
-
544
- refresh_follower_stats_btn.click(
545
- fn=run_follower_stats_tab_display, inputs=[token_state],
546
- outputs=[follower_stats_html, fs_plot_monthly_gains, fs_plot_seniority, fs_plot_industry],
547
- show_progress="full"
548
- )
549
 
550
- # --- NEW TABS FOR AGENTIC PIPELINE RESULTS ---
551
  with gr.TabItem("5️⃣ Agentic Analysis Report", id="tab_agentic_report", visible=AGENTIC_MODULES_LOADED):
552
  gr.Markdown("## 🤖 Comprehensive Analysis Report (AI Generated)")
553
- gr.Markdown("This report is generated by an AI agent based on the selected date range. Please review critically.")
554
- agentic_report_display_md = gr.Markdown("Apply a date filter in Tab 2 to generate the report.")
555
- if not AGENTIC_MODULES_LOADED:
556
- gr.Markdown("🔴 **Error:** Agentic pipeline modules could not be loaded. This tab is disabled.")
557
-
558
 
559
  with gr.TabItem("6️⃣ Agentic OKRs & Tasks", id="tab_agentic_okrs", visible=AGENTIC_MODULES_LOADED):
560
- gr.Markdown("## 🎯 AI Generated OKRs and Actionable Tasks")
561
- gr.Markdown("Based on the analysis, the AI agent has proposed the following Objectives and Key Results (OKRs), along with actionable tasks. Select Key Results you want to focus on to see detailed tasks.")
562
- if not AGENTIC_MODULES_LOADED:
563
- gr.Markdown("🔴 **Error:** Agentic pipeline modules could not be loaded. This tab is disabled.")
564
-
565
  with gr.Row():
566
  with gr.Column(scale=1):
567
- gr.Markdown("### Suggested Key Results")
568
- key_results_cbg = gr.CheckboxGroup(
569
- label="Select Key Results",
570
- choices=[], # Will be populated dynamically
571
- value=[],
572
- interactive=True
573
- )
574
  with gr.Column(scale=3):
575
  gr.Markdown("### Detailed OKRs and Tasks for Selected Key Results")
576
- okr_detail_display_md = gr.Markdown("Select Key Results from the left to see details, or apply a date filter in Tab 2 to generate OKRs.")
577
-
578
  def update_okr_display_on_selection(selected_kr_unique_ids: list, raw_orchestration_results: dict, all_krs_for_selection: list):
579
- """
580
- Updates the Markdown display for OKRs based on selected Key Results.
581
- If no KRs are selected, it might show all, or a prompt.
582
- """
583
- if not raw_orchestration_results or not AGENTIC_MODULES_LOADED:
584
- return gr.update(value="No orchestration data available or agentic modules not loaded. Apply a date filter first.")
585
-
586
  actionable_okrs_dict = raw_orchestration_results.get("actionable_okrs_and_tasks")
587
- if not actionable_okrs_dict or not isinstance(actionable_okrs_dict.get("okrs"), list):
588
- return gr.update(value="No OKRs found in the orchestration results.")
589
-
590
  okrs_list = actionable_okrs_dict["okrs"]
591
-
592
- # Create a mapping from unique_kr_id to (okr_idx, kr_idx)
593
- kr_id_to_indices = {
594
- kr_info['unique_kr_id']: (kr_info['okr_index'], kr_info['kr_index'])
595
- for kr_info in all_krs_for_selection
596
- }
597
-
598
- # Determine which KRs are selected for each OKR
599
- # okr_idx -> list of selected kr_indices for that OKR
600
  selected_krs_by_okr_idx = defaultdict(list)
601
- if selected_kr_unique_ids: # If specific KRs are selected
602
  for kr_unique_id in selected_kr_unique_ids:
603
- if kr_unique_id in kr_id_to_indices:
604
- okr_idx, kr_idx = kr_id_to_indices[kr_unique_id]
605
- selected_krs_by_okr_idx[okr_idx].append(kr_idx)
606
-
607
  output_md_parts = []
608
- if not okrs_list:
609
- output_md_parts.append("No OKRs were generated.")
610
  else:
611
  for okr_idx, okr_data in enumerate(okrs_list):
612
- # If specific KRs were selected, only display OKRs that have at least one selected KR,
613
- # or if no KRs are selected at all, display all OKRs with all their KRs.
614
- accepted_indices_for_this_okr = selected_krs_by_okr_idx.get(okr_idx) # This will be None if no KRs from this OKR are selected
615
-
616
- if selected_kr_unique_ids: # User has made a selection
617
- if accepted_indices_for_this_okr is not None : # Only show this OKR if some of its KRs are selected
618
- output_md_parts.append(format_single_okr_for_display(
619
- okr_data,
620
- accepted_kr_indices=accepted_indices_for_this_okr,
621
- okr_main_index=okr_idx
622
- ))
623
- else: # No KRs selected by user, show all OKRs with all their KRs
624
- output_md_parts.append(format_single_okr_for_display(
625
- okr_data,
626
- accepted_kr_indices=None, # None means show all KRs for this OKR
627
- okr_main_index=okr_idx
628
- ))
629
-
630
- if not output_md_parts and selected_kr_unique_ids: # User made selection, but no matching OKRs displayed
631
- final_md = "No OKRs match the current Key Result selection, or the selected Key Results do not have detailed tasks."
632
- elif not output_md_parts and not selected_kr_unique_ids: # No user selection and no OKRs at all
633
- final_md = "No OKRs generated. Please apply a filter in Tab 2."
634
- else:
635
- final_md = "\n\n---\n\n".join(output_md_parts)
636
-
637
  return gr.update(value=final_md)
638
-
639
  if AGENTIC_MODULES_LOADED:
640
- key_results_cbg.change(
641
- fn=update_okr_display_on_selection,
642
- inputs=[key_results_cbg, orchestration_raw_results_st, key_results_for_selection_st],
643
- outputs=[okr_detail_display_md]
644
- )
645
-
646
- # --- END NEW TABS ---
647
 
648
- # --- REFRESH ANALYTICS (NOW ASYNC and includes AGENTIC PIPELINE) ---
649
- async def refresh_all_analytics_ui_elements(current_token_state_val, date_filter_val, custom_start_val, custom_end_val, current_chat_histories_val):
650
- logging.info("Refreshing all analytics UI elements and resetting actions/chat.")
651
  start_time = time.time()
652
-
653
- # 1. Standard Analytics Plot Generation
654
  plot_gen_results = update_analytics_plots_figures(current_token_state_val, date_filter_val, custom_start_val, custom_end_val, plot_configs)
655
  status_msg, gen_figs, new_summaries = plot_gen_results[0], plot_gen_results[1:-1], plot_gen_results[-1]
656
-
657
- all_updates = [status_msg]
658
- 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))])
659
-
660
- # UI Resets for Analytics Tab Action Panel
661
- all_updates.extend([
662
- gr.update(visible=False), # global_actions_column_ui
663
- gr.update(value=[], visible=False), # insights_chatbot_ui (value & visibility)
664
- gr.update(value="", visible=False), # insights_chat_input_ui (value & visibility)
665
- gr.update(visible=False), # insights_suggestions_row_ui
666
- gr.update(value="S1"), gr.update(value="S2"), gr.update(value="S3"), # suggestion_btns
667
- gr.update(value="Formula details here.", visible=False), # formula_display_markdown_ui (value & visibility)
668
- gr.update(visible=False) # formula_close_hint_md
669
- ])
670
-
671
- # State Resets for Analytics Tab
672
- all_updates.extend([
673
- None, # active_panel_action_state
674
- None, # current_chat_plot_id_st
675
- {}, # chat_histories_st (resetting, new insights will be generated if panel opened)
676
- new_summaries # plot_data_for_chatbot_st
677
- ])
678
-
679
- # Plot button and panel visibility resets for Analytics Tab
680
- for _ in plot_configs:
681
- all_updates.extend([
682
- gr.update(value=BOMB_ICON),
683
- gr.update(value=FORMULA_ICON),
684
- gr.update(value=EXPLORE_ICON),
685
- gr.update(visible=True) # panel_component visibility
686
- ])
687
-
688
- all_updates.append(None) # explored_plot_id_state reset
689
- all_updates.extend([gr.update(visible=True)] * num_unique_sections) # section_title visibility reset
690
-
691
- # 2. Agentic Pipeline Execution
692
- agentic_status_update = "Agentic pipeline processing..."
693
- # Placeholder for agentic UI updates initially
694
- agentic_report_md_update = gr.update(value="Analisi AI in corso...")
695
- key_results_cbg_update = gr.update(choices=[], value=[], interactive=False)
696
- okr_detail_display_md_update = gr.update(value="Dettagli OKR in corso di generazione...")
697
- orchestration_results_update = None
698
- selected_krs_update = []
699
- krs_for_selection_update = []
700
-
701
- if AGENTIC_MODULES_LOADED:
702
- try:
703
- logging.info("Starting agentic pipeline...")
704
- orchestration_output = await run_full_analytics_orchestration(
705
- current_token_state_val, date_filter_val, custom_start_val, custom_end_val
706
- )
707
- agentic_status_update = "Agentic pipeline completed."
708
- logging.info(f"Agentic pipeline finished. Output keys: {orchestration_output.keys() if orchestration_output else 'None'}")
709
-
710
-
711
- if orchestration_output:
712
- orchestration_results_update = orchestration_output # Store raw results
713
-
714
- # Format report for Tab 5
715
- report_str = orchestration_output.get('comprehensive_analysis_report')
716
- agentic_report_md_update = gr.update(value=format_report_to_markdown(report_str))
717
-
718
- # Prepare KRs for Tab 6
719
- actionable_okrs = orchestration_output.get('actionable_okrs_and_tasks')
720
- krs_for_ui_selection_list = extract_key_results_for_selection(actionable_okrs)
721
- krs_for_selection_update = krs_for_ui_selection_list # Store for later use in event handler
722
-
723
- kr_choices_for_cbg = [(kr['kr_description'], kr['unique_kr_id']) for kr in krs_for_ui_selection_list]
724
- key_results_cbg_update = gr.update(choices=kr_choices_for_cbg, value=[], interactive=True)
725
-
726
- # Initially display all OKRs in Tab 6
727
- all_okrs_md_parts = []
728
- if actionable_okrs and isinstance(actionable_okrs.get("okrs"), list):
729
- for okr_idx, okr_item in enumerate(actionable_okrs["okrs"]):
730
- all_okrs_md_parts.append(format_single_okr_for_display(okr_item, accepted_kr_indices=None, okr_main_index=okr_idx))
731
-
732
- if not all_okrs_md_parts:
733
- okr_detail_display_md_update = gr.update(value="Nessun OKR generato o trovato.")
734
- else:
735
- okr_detail_display_md_update = gr.update(value="\n\n---\n\n".join(all_okrs_md_parts))
736
- selected_krs_update = [] # Reset selection
737
-
738
- else:
739
- agentic_report_md_update = gr.update(value="Nessun report generato dalla pipeline AI.")
740
- key_results_cbg_update = gr.update(choices=[], value=[], interactive=False)
741
- okr_detail_display_md_update = gr.update(value="Nessun OKR generato o errore nella pipeline AI.")
742
- orchestration_results_update = None
743
- selected_krs_update = []
744
- krs_for_selection_update = []
745
-
746
- except Exception as e:
747
- logging.error(f"Error during agentic pipeline execution: {e}", exc_info=True)
748
- agentic_status_update = f"Errore pipeline AI: {e}"
749
- agentic_report_md_update = gr.update(value=f"Errore durante la generazione del report AI: {e}")
750
- key_results_cbg_update = gr.update(choices=[], value=[], interactive=False)
751
- okr_detail_display_md_update = gr.update(value=f"Errore durante la generazione degli OKR AI: {e}")
752
- orchestration_results_update = None
753
- selected_krs_update = []
754
- krs_for_selection_update = []
755
- else: # AGENTIC_MODULES_LOADED is False
756
- agentic_status_update = "Moduli AI non caricati. Pipeline saltata."
757
- agentic_report_md_update = gr.update(value="Moduli AI non caricati. Report non disponibile.")
758
- key_results_cbg_update = gr.update(choices=[], value=[], interactive=False, visible=False) # Hide if not loaded
759
- okr_detail_display_md_update = gr.update(value="Moduli AI non caricati. OKR non disponibili.", visible=False) # Hide
760
- orchestration_results_update = None
761
- selected_krs_update = []
762
- krs_for_selection_update = []
763
-
764
-
765
- # Append agentic pipeline updates to all_updates
766
- # Order must match apply_filter_and_sync_outputs_list extension
767
- all_updates.extend([
768
- agentic_report_md_update, # For agentic_report_display_md
769
- key_results_cbg_update, # For key_results_cbg
770
- okr_detail_display_md_update, # For okr_detail_display_md
771
- orchestration_results_update, # For orchestration_raw_results_st
772
- selected_krs_update, # For selected_key_result_ids_st
773
- krs_for_selection_update # For key_results_for_selection_st
774
- ])
775
-
776
  end_time = time.time()
777
- logging.info(f"Analytics and Agentic refresh took {end_time - start_time:.2f} seconds.")
778
- logging.info(f"Prepared {len(all_updates)} updates for analytics refresh. Expected {15 + 5*len(plot_configs) + num_unique_sections + 6}.")
779
- # The status_msg from plot_gen_results is the first item. We can augment it.
780
- all_updates[0] = f"{status_msg} | {agentic_status_update}"
781
-
782
- return tuple(all_updates) # Return as tuple for Gradio
783
-
784
- # Define the list of outputs for apply_filter_btn and sync.then
785
- apply_filter_and_sync_outputs_list = [analytics_status_md]
786
- apply_filter_and_sync_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("plot_component", gr.update()) for pc in plot_configs])
787
-
788
- _ui_resets_for_filter = [
789
- global_actions_column_ui, insights_chatbot_ui, insights_chat_input_ui,
790
- insights_suggestions_row_ui, insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn,
791
- formula_display_markdown_ui, formula_close_hint_md
792
- ]
793
- apply_filter_and_sync_outputs_list.extend(_ui_resets_for_filter)
794
-
795
- _state_resets_for_filter = [active_panel_action_state, current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
796
- apply_filter_and_sync_outputs_list.extend(_state_resets_for_filter)
797
-
798
- for pc in plot_configs:
799
- pid = pc["id"]
800
- apply_filter_and_sync_outputs_list.extend([
801
- plot_ui_objects.get(pid, {}).get("bomb_button", gr.update()),
802
- plot_ui_objects.get(pid, {}).get("formula_button", gr.update()),
803
- plot_ui_objects.get(pid, {}).get("explore_button", gr.update()),
804
- plot_ui_objects.get(pid, {}).get("panel_component", gr.update())
805
- ])
806
-
807
- apply_filter_and_sync_outputs_list.append(explored_plot_id_state)
808
-
809
- apply_filter_and_sync_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
810
 
811
- # --- Add new outputs for agentic tabs and states ---
812
- apply_filter_and_sync_outputs_list.extend([
813
- agentic_report_display_md, # Tab 5 UI
814
- key_results_cbg, # Tab 6 UI (CheckboxGroup)
815
- okr_detail_display_md, # Tab 6 UI (Markdown display)
816
- orchestration_raw_results_st, # New State
817
- selected_key_result_ids_st, # New State
818
- key_results_for_selection_st # New State
819
- ])
820
- # --- End new outputs ---
821
-
822
  apply_filter_btn.click(
823
- fn=refresh_all_analytics_ui_elements, # Now async
824
- inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st],
825
- outputs=apply_filter_and_sync_outputs_list, show_progress="full"
 
826
  )
827
 
828
- # --- Sync Events (at the end of the app's 'with gr.Blocks()' context) ---
829
- sync_event_part1 = sync_data_btn.click(fn=sync_all_linkedin_data_orchestrator, inputs=[token_state], outputs=[sync_status_html_output, token_state], show_progress="full")
830
- sync_event_part2 = sync_event_part1.then(fn=process_and_store_bubble_token, inputs=[url_user_token_display, org_urn_display, token_state], outputs=[status_box, token_state, sync_data_btn], show_progress=False)
831
- sync_event_part3 = sync_event_part2.then(fn=display_main_dashboard, inputs=[token_state], outputs=[dashboard_display_html], show_progress=False)
832
- sync_event_final = sync_event_part3.then(
833
- fn=refresh_all_analytics_ui_elements, # Now async
834
- inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st],
835
- outputs=apply_filter_and_sync_outputs_list,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
836
  show_progress="full"
837
  )
838
 
@@ -841,13 +609,8 @@ if __name__ == "__main__":
841
  if not os.environ.get(LINKEDIN_CLIENT_ID_ENV_VAR): logging.warning(f"ATTENZIONE: '{LINKEDIN_CLIENT_ID_ENV_VAR}' non impostata.")
842
  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]):
843
  logging.warning("ATTENZIONE: Variabili Bubble non impostate.")
844
-
845
- if not AGENTIC_MODULES_LOADED:
846
- logging.warning("CRITICAL: Agentic pipeline modules (run_agentic_pipeline.py, insights_ui_generator.py) failed to load. Tabs 5 and 6 will be non-functional.")
847
- if not os.environ.get("GEMINI_API_KEY") and AGENTIC_MODULES_LOADED:
848
- logging.warning("ATTENZIONE: 'GEMINI_API_KEY' non impostata. La pipeline AI per le tab 5 e 6 potrebbe non funzionare.")
849
-
850
  try: logging.info(f"Matplotlib: {matplotlib.__version__}, Backend: {matplotlib.get_backend()}")
851
  except ImportError: logging.warning("Matplotlib non trovato.")
852
-
853
  app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
 
58
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
59
 
60
  # 1. Set Vertex AI usage preference (if applicable)
61
+ os.environ["GOOGLE_GENAI_USE_VERTEXAI"] = "False"
62
 
63
  # 2. Get your API key from your chosen environment variable name
64
+ user_provided_api_key = os.environ.get("GEMINI_API_KEY")
 
65
 
66
  if user_provided_api_key:
67
  os.environ["GOOGLE_API_KEY"] = user_provided_api_key
68
+ logging.info("GOOGLE_API_KEY environment variable has been set from GEMINI_API_KEY.")
69
  else:
70
+ logging.error(f"CRITICAL ERROR: The API key environment variable 'GEMINI_API_KEY' was not found. The application may not function correctly.")
71
 
72
 
73
  # --- Gradio UI Blocks ---
 
90
 
91
  # --- NEW STATES FOR AGENTIC PIPELINE ---
92
  orchestration_raw_results_st = gr.State(None)
 
93
  key_results_for_selection_st = gr.State([])
 
94
  selected_key_result_ids_st = gr.State([])
95
 
96
 
 
104
  def initial_load_sequence(url_token, org_urn_val, current_state):
105
  status_msg, new_state, btn_update = process_and_store_bubble_token(url_token, org_urn_val, current_state)
106
  dashboard_content = display_main_dashboard(new_state)
 
 
 
 
107
  return status_msg, new_state, btn_update, dashboard_content
108
 
109
  with gr.Tabs() as tabs:
 
113
  sync_status_html_output = gr.HTML("<p style='text-align:center;'>Stato sincronizzazione...</p>")
114
  dashboard_display_html = gr.HTML("<p style='text-align:center;'>Caricamento dashboard...</p>")
115
 
116
+ # Initial load sequence will be defined later with .then() for agentic pipeline
117
+ # org_urn_display.change(...)
 
 
 
 
118
 
119
  with gr.TabItem("2️⃣ Analisi", id="tab_analytics"):
120
  gr.Markdown("## 📈 Analisi Performance LinkedIn")
121
+ gr.Markdown("Seleziona un intervallo di date per i grafici. Clicca i pulsanti (💣 Insights, ƒ Formula, 🧭 Esplora) su un grafico per azioni.")
122
+ analytics_status_md = gr.Markdown("Stato analisi grafici...")
123
+ # Agentic pipeline status will be moved to Tab 5
124
 
125
  with gr.Row():
126
  date_filter_selector = gr.Radio(
127
  ["Sempre", "Ultimi 7 Giorni", "Ultimi 30 Giorni", "Intervallo Personalizzato"],
128
+ label="Seleziona Intervallo Date per Grafici", value="Sempre", scale=3 # Clarified label
129
  )
130
  with gr.Column(scale=2):
131
  custom_start_date_picker = gr.DateTime(label="Data Inizio", visible=False, include_time=False, type="datetime")
132
  custom_end_date_picker = gr.DateTime(label="Data Fine", visible=False, include_time=False, type="datetime")
133
 
134
+ apply_filter_btn = gr.Button("🔍 Applica Filtro & Aggiorna Grafici", variant="primary") # Clarified button text
135
 
136
  def toggle_custom_date_pickers(selection):
137
  is_custom = selection == "Intervallo Personalizzato"
 
169
  unique_ordered_sections = list(OrderedDict.fromkeys(pc["section"] for pc in plot_configs))
170
  num_unique_sections = len(unique_ordered_sections)
171
 
172
+ active_panel_action_state = gr.State(None)
173
+ explored_plot_id_state = gr.State(None)
174
 
175
+ plot_ui_objects = {}
176
+ section_titles_map = {}
177
 
178
  with gr.Row(equal_height=False):
179
  with gr.Column(scale=8) as plots_area_col:
 
184
  logging.error("section_titles_map from build_analytics_tab_plot_area is incomplete.")
185
  for sec_name in unique_ordered_sections:
186
  if sec_name not in section_titles_map:
187
+ section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)")
188
  else:
189
  logging.error("build_analytics_tab_plot_area did not return a tuple of (plot_ui_objects, section_titles_map).")
190
+ plot_ui_objects = ui_elements_tuple if isinstance(ui_elements_tuple, dict) else {}
191
+ for sec_name in unique_ordered_sections:
192
  section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)")
193
 
194
 
195
+ with gr.Column(scale=4, visible=False) as global_actions_column_ui:
196
  gr.Markdown("### 💡 Azioni Contestuali Grafico")
197
  insights_chatbot_ui = gr.Chatbot(
198
  label="Chat Insights", type="messages", height=450,
 
211
  formula_display_markdown_ui = gr.Markdown(
212
  "I dettagli sulla formula/metodologia appariranno qui.", visible=False
213
  )
214
+ formula_close_hint_md = gr.Markdown(
215
+ "<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>",
216
  visible=False
217
  )
218
 
 
219
  async def handle_panel_action(
220
  plot_id_clicked: str, action_type: str, current_active_action_from_state: dict,
221
  current_chat_histories: dict, current_chat_plot_id: str,
222
+ current_plot_data_for_chatbot: dict, current_explored_plot_id: str
223
  ):
224
  logging.info(f"Panel Action: '{action_type}' for plot '{plot_id_clicked}'. Active: {current_active_action_from_state}, Explored: {current_explored_plot_id}")
 
225
  clicked_plot_config = next((p for p in plot_configs if p["id"] == plot_id_clicked), None)
226
  if not clicked_plot_config:
227
  logging.error(f"Config not found for plot_id {plot_id_clicked}")
228
  num_plots = len(plot_configs)
229
+ error_list_len = 15 + (4 * num_plots) + num_unique_sections
230
  error_list = [gr.update()] * error_list_len
231
+ error_list[11] = current_active_action_from_state; error_list[12] = current_chat_plot_id
232
+ error_list[13] = current_chat_histories; error_list[14] = current_explored_plot_id
 
 
233
  return error_list
234
+ clicked_plot_label = clicked_plot_config["label"]; clicked_plot_section = clicked_plot_config["section"]
 
 
235
  hypothetical_new_active_state = {"plot_id": plot_id_clicked, "type": action_type}
236
  is_toggling_off = current_active_action_from_state == hypothetical_new_active_state
 
237
  action_col_visible_update = gr.update(visible=False)
238
  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)
239
+ formula_display_visible_update = gr.update(visible=False); formula_close_hint_visible_update = gr.update(visible=False)
 
240
  chatbot_content_update, s1_upd, s2_upd, s3_upd, formula_content_update = gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
 
241
  new_active_action_state_to_set, new_current_chat_plot_id = None, current_chat_plot_id
242
  updated_chat_histories, new_explored_plot_id_to_set = current_chat_histories, current_explored_plot_id
243
+ generated_panel_vis_updates = []; generated_bomb_btn_updates = []; generated_formula_btn_updates = []; generated_explore_btn_updates = []
 
 
 
 
244
  section_title_vis_updates = [gr.update()] * num_unique_sections
 
 
245
  if is_toggling_off:
246
+ new_active_action_state_to_set = None; action_col_visible_update = gr.update(visible=False)
 
247
  logging.info(f"Toggling OFF panel {action_type} for {plot_id_clicked}.")
248
+ for _ in plot_configs: generated_bomb_btn_updates.append(gr.update(value=BOMB_ICON)); generated_formula_btn_updates.append(gr.update(value=FORMULA_ICON))
249
+ if current_explored_plot_id:
 
 
 
 
250
  explored_cfg = next((p for p in plot_configs if p["id"] == current_explored_plot_id), None)
251
  explored_sec = explored_cfg["section"] if explored_cfg else None
252
+ for i, sec_name in enumerate(unique_ordered_sections): section_title_vis_updates[i] = gr.update(visible=(sec_name == explored_sec))
253
+ for cfg in plot_configs: is_exp = (cfg["id"] == current_explored_plot_id); generated_panel_vis_updates.append(gr.update(visible=is_exp)); generated_explore_btn_updates.append(gr.update(value=ACTIVE_ICON if is_exp else EXPLORE_ICON))
254
+ else:
 
 
 
 
255
  for i in range(num_unique_sections): section_title_vis_updates[i] = gr.update(visible=True)
256
+ for _ in plot_configs: generated_panel_vis_updates.append(gr.update(visible=True)); generated_explore_btn_updates.append(gr.update(value=EXPLORE_ICON))
 
 
 
257
  if action_type == "insights": new_current_chat_plot_id = None
258
+ else:
259
+ new_active_action_state_to_set = hypothetical_new_active_state; action_col_visible_update = gr.update(visible=True)
260
+ new_explored_plot_id_to_set = None
 
 
261
  logging.info(f"Toggling ON panel {action_type} for {plot_id_clicked}. Cancelling explore view if any.")
262
+ for i, sec_name in enumerate(unique_ordered_sections): section_title_vis_updates[i] = gr.update(visible=(sec_name == clicked_plot_section))
263
+ for cfg in plot_configs: generated_panel_vis_updates.append(gr.update(visible=(cfg["id"] == plot_id_clicked))); generated_explore_btn_updates.append(gr.update(value=EXPLORE_ICON))
264
+ for cfg_btn in plot_configs:
 
 
 
 
 
265
  is_act_ins = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "insights"}
266
  is_act_for = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "formula"}
267
+ generated_bomb_btn_updates.append(gr.update(value=ACTIVE_ICON if is_act_ins else BOMB_ICON)); generated_formula_btn_updates.append(gr.update(value=ACTIVE_ICON if is_act_for else FORMULA_ICON))
 
 
268
  if action_type == "insights":
269
  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)
270
  new_current_chat_plot_id = plot_id_clicked
271
  history = current_chat_histories.get(plot_id_clicked, [])
272
  summary = current_plot_data_for_chatbot.get(plot_id_clicked, f"No summary for '{clicked_plot_label}'.")
273
+ if not history:
274
  prompt, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
275
  llm_hist = [{"role": "user", "content": prompt}]
276
+ resp = await generate_llm_response(prompt, plot_id_clicked, clicked_plot_label, llm_hist, summary)
277
+ history = [{"role": "assistant", "content": resp}]; updated_chat_histories = {**current_chat_histories, plot_id_clicked: history}
278
+ else: _, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
 
 
 
279
  chatbot_content_update = gr.update(value=history)
280
  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")
 
281
  elif action_type == "formula":
282
+ formula_display_visible_update = gr.update(visible=True); formula_close_hint_visible_update = gr.update(visible=True)
 
283
  f_key = PLOT_ID_TO_FORMULA_KEY_MAP.get(plot_id_clicked)
284
  f_text = f"**Formula/Methodology for: {clicked_plot_label}** (ID: `{plot_id_clicked}`)\n\n"
285
+ if f_key and f_key in PLOT_FORMULAS: f_data = PLOT_FORMULAS[f_key]; 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']])
 
 
286
  else: f_text += "(No detailed formula information found.)"
287
+ formula_content_update = gr.update(value=f_text); new_current_chat_plot_id = None
288
+ final_updates = [action_col_visible_update, insights_chatbot_visible_update, chatbot_content_update, insights_chat_input_visible_update, insights_suggestions_row_visible_update, s1_upd, s2_upd, s3_upd, formula_display_visible_update, formula_content_update, formula_close_hint_visible_update, new_active_action_state_to_set, new_current_chat_plot_id, updated_chat_histories, new_explored_plot_id_to_set]
289
+ final_updates.extend(generated_panel_vis_updates); final_updates.extend(generated_bomb_btn_updates); final_updates.extend(generated_formula_btn_updates); final_updates.extend(generated_explore_btn_updates); final_updates.extend(section_title_vis_updates)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  logging.debug(f"handle_panel_action returning {len(final_updates)} updates. Expected {15 + 4*len(plot_configs) + num_unique_sections}.")
291
  return final_updates
292
 
 
295
  current_history_for_plot = chat_histories.get(current_plot_id, [])
296
  if not isinstance(current_history_for_plot, list): current_history_for_plot = []
297
  yield current_history_for_plot, gr.update(value=""), chat_histories; return
 
298
  cfg = next((p for p in plot_configs if p["id"] == current_plot_id), None)
299
  lbl = cfg["label"] if cfg else "Selected Plot"
300
  summary = current_plot_data_for_chatbot.get(current_plot_id, f"No summary for '{lbl}'.")
 
301
  hist_for_plot = chat_histories.get(current_plot_id, [])
302
+ if not isinstance(hist_for_plot, list): hist_for_plot = []
 
303
  hist = hist_for_plot.copy() + [{"role": "user", "content": user_message}]
304
+ yield hist, gr.update(value=""), chat_histories
305
+ resp = await generate_llm_response(user_message, current_plot_id, lbl, hist, summary)
 
306
  hist.append({"role": "assistant", "content": resp})
 
307
  updated_chat_histories = {**chat_histories, current_plot_id: hist}
308
  yield hist, "", updated_chat_histories
309
 
 
310
  async def handle_suggested_question_click(suggestion_text: str, current_plot_id: str, chat_histories: dict, current_plot_data_for_chatbot: dict):
311
  if not current_plot_id or not suggestion_text.strip() or suggestion_text == "N/A":
312
  current_history_for_plot = chat_histories.get(current_plot_id, [])
313
  if not isinstance(current_history_for_plot, list): current_history_for_plot = []
314
  yield current_history_for_plot, gr.update(value=""), chat_histories; return
 
315
  async for update_chunk in handle_chat_message_submission(suggestion_text, current_plot_id, chat_histories, current_plot_data_for_chatbot):
316
  yield update_chunk
317
 
 
318
  def handle_explore_click(plot_id_clicked, current_explored_plot_id_from_state, current_active_panel_action_state):
 
319
  logging.info(f"Explore Click: Plot '{plot_id_clicked}'. Current Explored: {current_explored_plot_id_from_state}. Active Panel: {current_active_panel_action_state}")
320
  num_plots = len(plot_configs)
321
+ if not plot_ui_objects:
322
  logging.error("plot_ui_objects not populated for handle_explore_click.")
323
+ error_list_len = 4 + (4 * num_plots) + num_unique_sections; error_list = [gr.update()] * error_list_len
324
+ error_list[0] = current_explored_plot_id_from_state; error_list[2] = current_active_panel_action_state
 
 
325
  return error_list
 
326
  new_explored_id_to_set = None
327
  is_toggling_off_explore = (plot_id_clicked == current_explored_plot_id_from_state)
328
+ action_col_upd = gr.update(); new_active_panel_state_upd = current_active_panel_action_state; formula_hint_upd = gr.update(visible=False)
329
+ panel_vis_updates = []; explore_btns_updates = []; bomb_btns_updates = []; formula_btns_updates = []
 
 
 
 
 
 
 
330
  section_title_vis_updates = [gr.update()] * num_unique_sections
 
331
  clicked_cfg = next((p for p in plot_configs if p["id"] == plot_id_clicked), None)
332
  sec_of_clicked = clicked_cfg["section"] if clicked_cfg else None
 
333
  if is_toggling_off_explore:
334
+ new_explored_id_to_set = None
335
  logging.info(f"Stopping explore for {plot_id_clicked}. All plots/sections to be visible.")
336
  for i in range(num_unique_sections): section_title_vis_updates[i] = gr.update(visible=True)
337
+ for _ in plot_configs: panel_vis_updates.append(gr.update(visible=True)); explore_btns_updates.append(gr.update(value=EXPLORE_ICON)); bomb_btns_updates.append(gr.update()); formula_btns_updates.append(gr.update())
338
+ else:
 
 
 
 
339
  new_explored_id_to_set = plot_id_clicked
340
  logging.info(f"Exploring {plot_id_clicked}. Hiding other plots/sections.")
341
+ for i, sec_name in enumerate(unique_ordered_sections): section_title_vis_updates[i] = gr.update(visible=(sec_name == sec_of_clicked))
342
+ for cfg in plot_configs: is_target = (cfg["id"] == new_explored_id_to_set); panel_vis_updates.append(gr.update(visible=is_target)); explore_btns_updates.append(gr.update(value=ACTIVE_ICON if is_target else EXPLORE_ICON))
 
 
 
 
 
343
  if current_active_panel_action_state:
344
  logging.info("Closing active insight/formula panel due to explore click.")
345
+ action_col_upd = gr.update(visible=False); new_active_panel_state_upd = None; formula_hint_upd = gr.update(visible=False)
346
+ bomb_btns_updates = [gr.update(value=BOMB_ICON) for _ in plot_configs]; formula_btns_updates = [gr.update(value=FORMULA_ICON) for _ in plot_configs]
347
+ else: bomb_btns_updates = [gr.update() for _ in plot_configs]; formula_btns_updates = [gr.update() for _ in plot_configs]
348
+ final_explore_updates = [new_explored_id_to_set, action_col_upd, new_active_panel_state_upd, formula_hint_upd]
349
+ final_explore_updates.extend(panel_vis_updates); final_explore_updates.extend(explore_btns_updates); final_explore_updates.extend(bomb_btns_updates); final_explore_updates.extend(formula_btns_updates); final_explore_updates.extend(section_title_vis_updates)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  logging.debug(f"handle_explore_click returning {len(final_explore_updates)} updates. Expected {4 + 4*len(plot_configs) + num_unique_sections}.")
351
  return final_explore_updates
352
 
353
+ _base_action_panel_ui_outputs = [global_actions_column_ui, insights_chatbot_ui, insights_chatbot_ui, insights_chat_input_ui, insights_suggestions_row_ui, insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn, formula_display_markdown_ui, formula_display_markdown_ui, formula_close_hint_md]
 
 
 
 
 
 
 
354
  _action_panel_state_outputs = [active_panel_action_state, current_chat_plot_id_st, chat_histories_st, explored_plot_id_state]
355
+ action_panel_outputs_list = _base_action_panel_ui_outputs + _action_panel_state_outputs
356
+ action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("panel_component", gr.update()) for pc in plot_configs]); action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("bomb_button", gr.update()) for pc in plot_configs]); action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs]); action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("explore_button", gr.update()) for pc in plot_configs]); action_panel_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections])
357
+ _explore_base_outputs = [explored_plot_id_state, global_actions_column_ui, active_panel_action_state, formula_close_hint_md]
358
+ explore_outputs_list = _explore_base_outputs
359
+ explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("panel_component", gr.update()) for pc in plot_configs]); explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("explore_button", gr.update()) for pc in plot_configs]); explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("bomb_button", gr.update()) for pc in plot_configs]); explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs]); explore_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections])
 
 
 
 
 
 
 
 
 
 
 
360
  action_click_inputs = [active_panel_action_state, chat_histories_st, current_chat_plot_id_st, plot_data_for_chatbot_st, explored_plot_id_state]
361
  explore_click_inputs = [explored_plot_id_state, active_panel_action_state]
 
362
  def create_panel_action_handler(p_id, action_type_str):
363
+ async def _handler(curr_active_val, curr_chats_val, curr_chat_pid, curr_plot_data, curr_explored_id): return await handle_panel_action(p_id, action_type_str, curr_active_val, curr_chats_val, curr_chat_pid, curr_plot_data, curr_explored_id)
 
364
  return _handler
 
365
  for config_item in plot_configs:
366
  plot_id = config_item["id"]
367
  if plot_id in plot_ui_objects:
368
  ui_obj = plot_ui_objects[plot_id]
369
+ if ui_obj.get("bomb_button"): 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}")
370
+ if ui_obj.get("formula_button"): 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}")
371
+ if ui_obj.get("explore_button"): 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}")
 
 
 
 
 
 
 
 
372
  else: logging.warning(f"UI object for plot_id '{plot_id}' not found for click handlers.")
 
 
373
  chat_submission_outputs = [insights_chatbot_ui, insights_chat_input_ui, chat_histories_st]
374
  chat_submission_inputs = [insights_chat_input_ui, current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
375
  insights_chat_input_ui.submit(fn=handle_chat_message_submission, inputs=chat_submission_inputs, outputs=chat_submission_outputs, api_name="submit_chat_message")
 
376
  suggestion_click_inputs_base = [current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
377
  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")
378
  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")
 
382
  refresh_mentions_display_btn = gr.Button("🔄 Aggiorna Visualizzazione Menzioni", variant="secondary")
383
  mentions_html = gr.HTML("Dati menzioni...")
384
  mentions_sentiment_dist_plot = gr.Plot(label="Distribuzione Sentiment Menzioni")
385
+ refresh_mentions_display_btn.click(fn=run_mentions_tab_display, inputs=[token_state], outputs=[mentions_html, mentions_sentiment_dist_plot], show_progress="full")
 
 
 
 
386
 
387
  with gr.TabItem("4️⃣ Statistiche Follower", id="tab_follower_stats"):
388
  refresh_follower_stats_btn = gr.Button("🔄 Aggiorna Visualizzazione Statistiche Follower", variant="secondary")
389
  follower_stats_html = gr.HTML("Statistiche follower...")
390
+ with gr.Row(): fs_plot_monthly_gains = gr.Plot(label="Guadagni Mensili Follower")
391
+ with gr.Row(): fs_plot_seniority = gr.Plot(label="Follower per Anzianità (Top 10 Organici)"); fs_plot_industry = gr.Plot(label="Follower per Settore (Top 10 Organici)")
392
+ refresh_follower_stats_btn.click(fn=run_follower_stats_tab_display, inputs=[token_state], outputs=[follower_stats_html, fs_plot_monthly_gains, fs_plot_seniority, fs_plot_industry], show_progress="full")
 
 
 
 
 
 
 
 
393
 
 
394
  with gr.TabItem("5️⃣ Agentic Analysis Report", id="tab_agentic_report", visible=AGENTIC_MODULES_LOADED):
395
  gr.Markdown("## 🤖 Comprehensive Analysis Report (AI Generated)")
396
+ # Moved agentic_pipeline_status_md here
397
+ agentic_pipeline_status_md = gr.Markdown("Stato Pipeline AI (filtro 'Sempre'): In attesa...", visible=True)
398
+ gr.Markdown("Questo report è generato da un agente AI con filtro 'Sempre' sui dati disponibili. Rivedi criticamente.")
399
+ agentic_report_display_md = gr.Markdown("La pipeline AI si avvierà automaticamente dopo il caricamento iniziale dei dati o dopo una sincronizzazione.")
400
+ if not AGENTIC_MODULES_LOADED: gr.Markdown("🔴 **Error:** Agentic pipeline modules could not be loaded. This tab is disabled.")
401
 
402
  with gr.TabItem("6️⃣ Agentic OKRs & Tasks", id="tab_agentic_okrs", visible=AGENTIC_MODULES_LOADED):
403
+ gr.Markdown("## 🎯 AI Generated OKRs and Actionable Tasks (filtro 'Sempre')")
404
+ gr.Markdown("Basato sull'analisi AI (filtro 'Sempre'), l'agente ha proposto i seguenti OKR e task. Seleziona i Key Results per dettagli.")
405
+ if not AGENTIC_MODULES_LOADED: gr.Markdown("🔴 **Error:** Agentic pipeline modules could not be loaded. This tab is disabled.")
 
 
406
  with gr.Row():
407
  with gr.Column(scale=1):
408
+ gr.Markdown("### Suggested Key Results (da analisi 'Sempre')")
409
+ key_results_cbg = gr.CheckboxGroup(label="Select Key Results", choices=[], value=[], interactive=True)
 
 
 
 
 
410
  with gr.Column(scale=3):
411
  gr.Markdown("### Detailed OKRs and Tasks for Selected Key Results")
412
+ okr_detail_display_md = gr.Markdown("I dettagli OKR appariranno qui dopo l'esecuzione della pipeline AI.")
 
413
  def update_okr_display_on_selection(selected_kr_unique_ids: list, raw_orchestration_results: dict, all_krs_for_selection: list):
414
+ if not raw_orchestration_results or not AGENTIC_MODULES_LOADED: return gr.update(value="Nessun dato dalla pipeline AI o moduli non caricati.")
 
 
 
 
 
 
415
  actionable_okrs_dict = raw_orchestration_results.get("actionable_okrs_and_tasks")
416
+ if not actionable_okrs_dict or not isinstance(actionable_okrs_dict.get("okrs"), list): return gr.update(value="Nessun OKR trovato nei risultati della pipeline.")
 
 
417
  okrs_list = actionable_okrs_dict["okrs"]
418
+ kr_id_to_indices = {kr_info['unique_kr_id']: (kr_info['okr_index'], kr_info['kr_index']) for kr_info in all_krs_for_selection}
 
 
 
 
 
 
 
 
419
  selected_krs_by_okr_idx = defaultdict(list)
420
+ if selected_kr_unique_ids:
421
  for kr_unique_id in selected_kr_unique_ids:
422
+ if kr_unique_id in kr_id_to_indices: okr_idx, kr_idx = kr_id_to_indices[kr_unique_id]; selected_krs_by_okr_idx[okr_idx].append(kr_idx)
 
 
 
423
  output_md_parts = []
424
+ if not okrs_list: output_md_parts.append("Nessun OKR generato.")
 
425
  else:
426
  for okr_idx, okr_data in enumerate(okrs_list):
427
+ accepted_indices_for_this_okr = selected_krs_by_okr_idx.get(okr_idx)
428
+ if selected_kr_unique_ids:
429
+ if accepted_indices_for_this_okr is not None: output_md_parts.append(format_single_okr_for_display(okr_data, accepted_kr_indices=accepted_indices_for_this_okr, okr_main_index=okr_idx))
430
+ else: output_md_parts.append(format_single_okr_for_display(okr_data, accepted_kr_indices=None, okr_main_index=okr_idx))
431
+ if not output_md_parts and selected_kr_unique_ids: final_md = "Nessun OKR corrisponde alla selezione corrente o i KR selezionati non hanno task dettagliati."
432
+ elif not output_md_parts and not selected_kr_unique_ids: final_md = "Nessun OKR generato."
433
+ else: final_md = "\n\n---\n\n".join(output_md_parts)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  return gr.update(value=final_md)
 
435
  if AGENTIC_MODULES_LOADED:
436
+ key_results_cbg.change(fn=update_okr_display_on_selection, inputs=[key_results_cbg, orchestration_raw_results_st, key_results_for_selection_st], outputs=[okr_detail_display_md])
 
 
 
 
 
 
437
 
438
+ async def refresh_analytics_graphs_ui(current_token_state_val, date_filter_val, custom_start_val, custom_end_val, current_chat_histories_val):
439
+ logging.info("Refreshing analytics graph UI elements and resetting actions/chat.")
 
440
  start_time = time.time()
 
 
441
  plot_gen_results = update_analytics_plots_figures(current_token_state_val, 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
+ all_updates = [status_msg]
444
+ 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))])
445
+ all_updates.extend([gr.update(visible=False), gr.update(value=[], visible=False), gr.update(value="", visible=False), gr.update(visible=False), gr.update(value="S1"), gr.update(value="S2"), gr.update(value="S3"), gr.update(value="Formula details here.", visible=False), gr.update(visible=False)])
446
+ all_updates.extend([None, None, {}, new_summaries])
447
+ for _ in plot_configs: all_updates.extend([gr.update(value=BOMB_ICON), gr.update(value=FORMULA_ICON), gr.update(value=EXPLORE_ICON), gr.update(visible=True)])
448
+ all_updates.append(None)
449
+ all_updates.extend([gr.update(visible=True)] * num_unique_sections)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
  end_time = time.time()
451
+ logging.info(f"Analytics graph refresh took {end_time - start_time:.2f} seconds.")
452
+ expected_len = 15 + 5 * len(plot_configs) + num_unique_sections
453
+ logging.info(f"Prepared {len(all_updates)} updates for graph refresh. Expected {expected_len}.")
454
+ return tuple(all_updates)
455
+
456
+ async def run_agentic_pipeline_autonomously(current_token_state_val, request: gr.Request):
457
+ # Added request: gr.Request to check if it's the initial call or subsequent.
458
+ # This is a simple way to prevent multiple rapid fires if not needed,
459
+ # though ideally Gradio's event system handles this.
460
+ # For more robust control, a separate state could track if the pipeline has run for current data.
461
+ client_ip = request.client.host if request else "unknown_client"
462
+ logging.info(f"Agentic pipeline check triggered for token_state update from {client_ip}. Current token: {'Set' if current_token_state_val.get('token') else 'Not Set'}")
463
+
464
+ if not current_token_state_val or not current_token_state_val.get("token"):
465
+ logging.info("Agentic pipeline: Token not available in token_state. Skipping.")
466
+ yield ( # Yield initial "waiting" state if token not ready
467
+ gr.update(value="Pipeline AI: In attesa dei dati necessari..."), # agentic_report_display_md
468
+ gr.update(choices=[], value=[], interactive=False), # key_results_cbg
469
+ gr.update(value="Pipeline AI: In attesa dei dati necessari..."), # okr_detail_display_md
470
+ None, # orchestration_raw_results_st
471
+ [], # selected_key_result_ids_st
472
+ [], # key_results_for_selection_st
473
+ "Pipeline AI: In attesa dei dati..." # agentic_pipeline_status_md
474
+ )
475
+ return
476
+
477
+ logging.info("Agentic pipeline starting autonomously with 'Sempre' filter.")
478
+ yield (
479
+ gr.update(value="Analisi AI (Sempre) in corso..."),
480
+ gr.update(choices=[], value=[], interactive=False),
481
+ gr.update(value="Dettagli OKR (Sempre) in corso di generazione..."),
482
+ None, # orchestration_raw_results_st (don't overwrite with None if it already has results)
483
+ [], # selected_key_result_ids_st
484
+ [], # key_results_for_selection_st
485
+ "Esecuzione pipeline AI (Sempre)..."
486
+ )
487
+
488
+ if not AGENTIC_MODULES_LOADED:
489
+ logging.warning("Agentic modules not loaded. Skipping autonomous pipeline.")
490
+ yield (
491
+ gr.update(value="Moduli AI non caricati. Report non disponibile."),
492
+ gr.update(choices=[], value=[], interactive=False),
493
+ gr.update(value="Moduli AI non caricati. OKR non disponibili."),
494
+ None, [], [], "Pipeline AI: Moduli non caricati."
495
+ )
496
+ return
497
+
498
+ try:
499
+ date_filter_val_agentic = "Sempre"; custom_start_val_agentic = None; custom_end_val_agentic = None
500
+ orchestration_output = await run_full_analytics_orchestration(current_token_state_val, date_filter_val_agentic, custom_start_val_agentic, custom_end_val_agentic)
501
+ agentic_status_text = "Pipeline AI (Sempre) completata."
502
+ logging.info(f"Autonomous agentic pipeline finished. Output keys: {orchestration_output.keys() if orchestration_output else 'None'}")
503
+ if orchestration_output:
504
+ orchestration_results_update = orchestration_output
505
+ report_str = orchestration_output.get('comprehensive_analysis_report')
506
+ agentic_report_md_update = gr.update(value=format_report_to_markdown(report_str))
507
+ actionable_okrs = orchestration_output.get('actionable_okrs_and_tasks')
508
+ krs_for_ui_selection_list = extract_key_results_for_selection(actionable_okrs)
509
+ krs_for_selection_update = krs_for_ui_selection_list
510
+ kr_choices_for_cbg = [(kr['kr_description'], kr['unique_kr_id']) for kr in krs_for_ui_selection_list]
511
+ key_results_cbg_update = gr.update(choices=kr_choices_for_cbg, value=[], interactive=True)
512
+ all_okrs_md_parts = []
513
+ if actionable_okrs and isinstance(actionable_okrs.get("okrs"), list):
514
+ for okr_idx, okr_item in enumerate(actionable_okrs["okrs"]): all_okrs_md_parts.append(format_single_okr_for_display(okr_item, accepted_kr_indices=None, okr_main_index=okr_idx))
515
+ if not all_okrs_md_parts: okr_detail_display_md_update = gr.update(value="Nessun OKR generato o trovato (Sempre).")
516
+ else: okr_detail_display_md_update = gr.update(value="\n\n---\n\n".join(all_okrs_md_parts))
517
+ selected_krs_update = []
518
+ else:
519
+ agentic_report_md_update = gr.update(value="Nessun report generato dalla pipeline AI (Sempre).")
520
+ key_results_cbg_update = gr.update(choices=[], value=[], interactive=False)
521
+ okr_detail_display_md_update = gr.update(value="Nessun OKR generato o errore nella pipeline AI (Sempre).")
522
+ orchestration_results_update = None; selected_krs_update = []; krs_for_selection_update = []
523
+ yield (agentic_report_md_update, key_results_cbg_update, okr_detail_display_md_update, orchestration_results_update, selected_krs_update, krs_for_selection_update, agentic_status_text)
524
+ except Exception as e:
525
+ logging.error(f"Error during autonomous agentic pipeline execution: {e}", exc_info=True)
526
+ agentic_status_text = f"Errore pipeline AI (Sempre): {str(e)}"
527
+ yield (gr.update(value=f"Errore generazione report AI (Sempre): {str(e)}"), gr.update(choices=[], value=[], interactive=False), gr.update(value=f"Errore generazione OKR AI (Sempre): {str(e)}"), None, [], [], agentic_status_text)
528
+
529
+ graph_refresh_outputs_list = [analytics_status_md]
530
+ graph_refresh_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("plot_component", gr.update()) for pc in plot_configs])
531
+ _ui_resets_for_graphs = [global_actions_column_ui, insights_chatbot_ui, insights_chat_input_ui, insights_suggestions_row_ui, insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn, formula_display_markdown_ui, formula_close_hint_md]
532
+ graph_refresh_outputs_list.extend(_ui_resets_for_graphs)
533
+ _state_resets_for_graphs = [active_panel_action_state, current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
534
+ graph_refresh_outputs_list.extend(_state_resets_for_graphs)
535
+ for pc in plot_configs: pid = pc["id"]; graph_refresh_outputs_list.extend([plot_ui_objects.get(pid, {}).get("bomb_button", gr.update()), plot_ui_objects.get(pid, {}).get("formula_button", gr.update()), plot_ui_objects.get(pid, {}).get("explore_button", gr.update()), plot_ui_objects.get(pid, {}).get("panel_component", gr.update())])
536
+ graph_refresh_outputs_list.append(explored_plot_id_state)
537
+ graph_refresh_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections])
538
+
539
+ agentic_pipeline_outputs_list = [agentic_report_display_md, key_results_cbg, okr_detail_display_md, orchestration_raw_results_st, selected_key_result_ids_st, key_results_for_selection_st, agentic_pipeline_status_md]
540
 
541
+ graph_refresh_inputs = [token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st]
542
+ agentic_pipeline_inputs = [token_state] # Removed request for now, can be added back if needed for advanced logic
543
+
544
+ # Event for Apply Filter Button (only refreshes graphs)
 
 
 
 
 
 
 
545
  apply_filter_btn.click(
546
+ fn=refresh_analytics_graphs_ui,
547
+ inputs=graph_refresh_inputs,
548
+ outputs=graph_refresh_outputs_list,
549
+ show_progress="full"
550
  )
551
 
552
+ # --- Event Chains for Initial Load and Sync ---
553
+ # Initial Load
554
+ initial_load_event = org_urn_display.change(
555
+ fn=initial_load_sequence,
556
+ inputs=[url_user_token_display, org_urn_display, token_state],
557
+ outputs=[status_box, token_state, sync_data_btn, dashboard_display_html],
558
+ show_progress="full"
559
+ )
560
+ # After initial load, refresh graphs AND run agentic pipeline
561
+ initial_load_event.then(
562
+ fn=refresh_analytics_graphs_ui, # Refresh graphs with default "Sempre" filter
563
+ inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st], # date_filter_selector will provide "Sempre"
564
+ outputs=graph_refresh_outputs_list,
565
+ show_progress="full"
566
+ ).then(
567
+ fn=run_agentic_pipeline_autonomously, # Run agentic pipeline
568
+ inputs=agentic_pipeline_inputs, # Uses token_state
569
+ outputs=agentic_pipeline_outputs_list,
570
+ show_progress="minimal"
571
+ )
572
+
573
+ # Sync Process
574
+ sync_event_part1 = sync_data_btn.click(
575
+ fn=sync_all_linkedin_data_orchestrator,
576
+ inputs=[token_state],
577
+ outputs=[sync_status_html_output, token_state],
578
+ show_progress="full"
579
+ )
580
+ sync_event_part2 = sync_event_part1.then(
581
+ fn=process_and_store_bubble_token,
582
+ inputs=[url_user_token_display, org_urn_display, token_state],
583
+ outputs=[status_box, token_state, sync_data_btn], # token_state is updated here
584
+ show_progress=False
585
+ )
586
+ # After token_state is updated by sync, run agentic pipeline
587
+ sync_event_part2.then(
588
+ fn=run_agentic_pipeline_autonomously,
589
+ inputs=agentic_pipeline_inputs, # Uses updated token_state
590
+ outputs=agentic_pipeline_outputs_list,
591
+ show_progress="minimal"
592
+ )
593
+ # Separately, update dashboard and graphs after sync
594
+ sync_event_part3 = sync_event_part2.then(
595
+ fn=display_main_dashboard,
596
+ inputs=[token_state],
597
+ outputs=[dashboard_display_html],
598
+ show_progress=False
599
+ )
600
+ sync_event_graphs_after_sync = sync_event_part3.then(
601
+ fn=refresh_analytics_graphs_ui,
602
+ inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st],
603
+ outputs=graph_refresh_outputs_list,
604
  show_progress="full"
605
  )
606
 
 
609
  if not os.environ.get(LINKEDIN_CLIENT_ID_ENV_VAR): logging.warning(f"ATTENZIONE: '{LINKEDIN_CLIENT_ID_ENV_VAR}' non impostata.")
610
  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]):
611
  logging.warning("ATTENZIONE: Variabili Bubble non impostate.")
612
+ if not AGENTIC_MODULES_LOADED: logging.warning("CRITICAL: Agentic pipeline modules failed to load. Tabs 5 and 6 will be non-functional.")
613
+ if not os.environ.get("GEMINI_API_KEY") and AGENTIC_MODULES_LOADED: logging.warning("ATTENZIONE: 'GEMINI_API_KEY' non impostata. La pipeline AI per le tab 5 e 6 potrebbe non funzionare.")
 
 
 
 
614
  try: logging.info(f"Matplotlib: {matplotlib.__version__}, Backend: {matplotlib.get_backend()}")
615
  except ImportError: logging.warning("Matplotlib non trovato.")
 
616
  app.launch(server_name="0.0.0.0", server_port=7860, debug=True)