GuglielmoTor commited on
Commit
5a483f8
·
verified ·
1 Parent(s): e03d275

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +314 -142
app.py CHANGED
@@ -12,6 +12,7 @@ import time # For profiling if needed
12
  from datetime import datetime, timedelta # Added timedelta
13
  import numpy as np
14
  from collections import OrderedDict # To maintain section order
 
15
 
16
  # --- Module Imports ---
17
  from gradio_utils import get_url_user_token
@@ -32,10 +33,34 @@ from ui_generators import (
32
  from analytics_plot_generator import update_analytics_plots_figures, create_placeholder_plot
33
  from formulas import PLOT_FORMULAS
34
 
35
- # --- NEW CHATBOT MODULE IMPORTS ---
36
  from chatbot_prompts import get_initial_insight_prompt_and_suggestions # MODIFIED IMPORT
37
  from chatbot_handler import generate_llm_response
38
- # --- END NEW CHATBOT MODULE IMPORTS ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  # Configure logging
41
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
@@ -54,10 +79,16 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
54
  "config_eb_labels_col": "li_eb_label"
55
  })
56
 
 
57
  chat_histories_st = gr.State({})
58
  current_chat_plot_id_st = gr.State(None)
59
  plot_data_for_chatbot_st = gr.State({})
60
 
 
 
 
 
 
61
  gr.Markdown("# 🚀 LinkedIn Organization Dashboard")
62
  url_user_token_display = gr.Textbox(label="User Token (Nascosto)", interactive=False, visible=False)
63
  status_box = gr.Textbox(label="Stato Generale Token LinkedIn", interactive=False, value="Inizializzazione...")
@@ -136,34 +167,30 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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,
@@ -182,13 +209,12 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
182
  formula_display_markdown_ui = gr.Markdown(
183
  "I dettagli sulla formula/metodologia appariranno qui.", visible=False
184
  )
185
- # NEW: Hint for closing the formula panel
186
- formula_close_hint_md = gr.Markdown(
187
  "<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>",
188
  visible=False
189
  )
190
 
191
-
192
  async def handle_panel_action(
193
  plot_id_clicked: str, action_type: str, current_active_action_from_state: dict,
194
  current_chat_histories: dict, current_chat_plot_id: str,
@@ -200,15 +226,8 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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
@@ -220,7 +239,6 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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,7 +248,6 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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 = []
@@ -239,17 +256,15 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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):
@@ -259,28 +274,25 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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"}
@@ -292,15 +304,14 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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")
@@ -315,32 +326,31 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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
@@ -349,18 +359,15 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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
 
@@ -371,29 +378,28 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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 = []
@@ -405,17 +411,15 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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):
@@ -425,24 +429,19 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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
  ]
@@ -456,35 +455,29 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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]
470
 
471
- action_panel_outputs_list = _base_action_panel_ui_outputs + _action_panel_state_outputs # 11 + 4 = 15
472
- action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("panel_component", gr.update()) for pc in plot_configs]) # +N (panels)
473
- action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("bomb_button", gr.update()) for pc in plot_configs]) # +N (bomb_button)
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]
481
- explore_outputs_list = _explore_base_outputs # 4
482
- explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("panel_component", gr.update()) for pc in plot_configs]) # +N (panels)
483
- explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("explore_button", gr.update()) for pc in plot_configs]) # +N (explore_button)
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,13 +491,18 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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
 
@@ -518,56 +516,49 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
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,
@@ -575,26 +566,21 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
575
  ]
576
  apply_filter_and_sync_outputs_list.extend(_ui_resets_for_filter)
577
 
578
- # 4. State Resets (4 elements)
579
  _state_resets_for_filter = [active_panel_action_state, current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
580
  apply_filter_and_sync_outputs_list.extend(_state_resets_for_filter)
581
 
582
- # 5. Plot-specific buttons (3 per plot) and panel visibility (1 per plot) = 4N elements
583
  for pc in plot_configs:
584
  pid = pc["id"]
585
  apply_filter_and_sync_outputs_list.extend([
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)
593
  apply_filter_and_sync_outputs_list.append(explored_plot_id_state)
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,
@@ -626,17 +612,203 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
626
  outputs=[follower_stats_html, fs_plot_monthly_gains, fs_plot_seniority, fs_plot_industry],
627
  show_progress="full"
628
  )
 
 
 
 
629
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  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")
631
  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)
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)
 
 
 
12
  from datetime import datetime, timedelta # Added timedelta
13
  import numpy as np
14
  from collections import OrderedDict # To maintain section order
15
+ import asyncio # For async operations with the new agent
16
 
17
  # --- Module Imports ---
18
  from gradio_utils import get_url_user_token
 
33
  from analytics_plot_generator import update_analytics_plots_figures, create_placeholder_plot
34
  from formulas import PLOT_FORMULAS
35
 
36
+ # --- EXISTING CHATBOT MODULE IMPORTS ---
37
  from chatbot_prompts import get_initial_insight_prompt_and_suggestions # MODIFIED IMPORT
38
  from chatbot_handler import generate_llm_response
39
+ # --- END EXISTING CHATBOT MODULE IMPORTS ---
40
+
41
+ # --- NEW EMPLOYER BRANDING AGENT MODULE IMPORTS ---
42
+ try:
43
+ from eb_agent_module import (
44
+ EmployerBrandingAgent,
45
+ GENERATION_CONFIG_PARAMS as EB_AGENT_GEN_CONFIG, # Rename to avoid conflict
46
+ LLM_MODEL_NAME as EB_AGENT_LLM_MODEL, # Rename
47
+ GEMINI_EMBEDDING_MODEL_NAME as EB_AGENT_EMBEDDING_MODEL, # Rename
48
+ df_rag_documents as eb_agent_default_rag_docs, # Rename
49
+ DEFAULT_SAFETY_SETTINGS as EB_AGENT_SAFETY_SETTINGS # Import safety settings
50
+ )
51
+ EB_AGENT_AVAILABLE = True
52
+ logging.info("Successfully imported EmployerBrandingAgent module.")
53
+ except ImportError as e:
54
+ logging.error(f"Failed to import EmployerBrandingAgent module: {e}", exc_info=True)
55
+ EB_AGENT_AVAILABLE = False
56
+ # Define dummy classes/variables if import fails, so app can still run
57
+ class EmployerBrandingAgent:
58
+ def __init__(self, *args, **kwargs): logging.error("EB Agent Dummy Class Initialized")
59
+ async def process_query(self, query, **kwargs): return "# Error: Employer Branding Agent module not loaded."
60
+ def update_dataframes(self, dfs): pass
61
+ def clear_chat_history(self): pass
62
+ EB_AGENT_GEN_CONFIG, EB_AGENT_LLM_MODEL, EB_AGENT_EMBEDDING_MODEL, eb_agent_default_rag_docs, EB_AGENT_SAFETY_SETTINGS = {}, None, None, pd.DataFrame(), {}
63
+
64
 
65
  # Configure logging
66
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(message)s')
 
79
  "config_eb_labels_col": "li_eb_label"
80
  })
81
 
82
+ # States for existing analytics tab chatbot
83
  chat_histories_st = gr.State({})
84
  current_chat_plot_id_st = gr.State(None)
85
  plot_data_for_chatbot_st = gr.State({})
86
 
87
+ # --- NEW: States for Employer Branding Agent Tab ---
88
+ eb_agent_chat_history_st = gr.State([])
89
+ # The agent instance itself will be created on-the-fly or managed if complex state is needed.
90
+ # For now, we'll re-initialize it with fresh data in the handler.
91
+
92
  gr.Markdown("# 🚀 LinkedIn Organization Dashboard")
93
  url_user_token_display = gr.Textbox(label="User Token (Nascosto)", interactive=False, visible=False)
94
  status_box = gr.Textbox(label="Stato Generale Token LinkedIn", interactive=False, value="Inizializzazione...")
 
167
  unique_ordered_sections = list(OrderedDict.fromkeys(pc["section"] for pc in plot_configs))
168
  num_unique_sections = len(unique_ordered_sections)
169
 
170
+ active_panel_action_state = gr.State(None)
171
+ explored_plot_id_state = gr.State(None)
172
 
173
  plot_ui_objects = {}
174
  section_titles_map = {}
175
 
176
  with gr.Row(equal_height=False):
177
  with gr.Column(scale=8) as plots_area_col:
 
178
  ui_elements_tuple = build_analytics_tab_plot_area(plot_configs)
179
  if isinstance(ui_elements_tuple, tuple) and len(ui_elements_tuple) == 2:
180
  plot_ui_objects, section_titles_map = ui_elements_tuple
 
181
  if not all(sec_name in section_titles_map for sec_name in unique_ordered_sections):
182
  logging.error("section_titles_map from build_analytics_tab_plot_area is incomplete.")
 
183
  for sec_name in unique_ordered_sections:
184
  if sec_name not in section_titles_map:
185
+ section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)")
 
186
  else:
187
  logging.error("build_analytics_tab_plot_area did not return a tuple of (plot_ui_objects, section_titles_map).")
188
+ plot_ui_objects = ui_elements_tuple if isinstance(ui_elements_tuple, dict) else {}
189
+ for sec_name in unique_ordered_sections:
190
  section_titles_map[sec_name] = gr.Markdown(f"### {sec_name} (Error Placeholder)")
191
 
192
 
193
+ with gr.Column(scale=4, visible=False) as global_actions_column_ui:
194
  gr.Markdown("### 💡 Azioni Contestuali Grafico")
195
  insights_chatbot_ui = gr.Chatbot(
196
  label="Chat Insights", type="messages", height=450,
 
209
  formula_display_markdown_ui = gr.Markdown(
210
  "I dettagli sulla formula/metodologia appariranno qui.", visible=False
211
  )
212
+ formula_close_hint_md = gr.Markdown( # Component for the hint's visibility
 
213
  "<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>",
214
  visible=False
215
  )
216
 
217
+ # --- ASYNC HANDLERS FOR ANALYTICS TAB ---
218
  async def handle_panel_action(
219
  plot_id_clicked: str, action_type: str, current_active_action_from_state: dict,
220
  current_chat_histories: dict, current_chat_plot_id: str,
 
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
232
  error_list[12] = current_chat_plot_id
233
  error_list[13] = current_chat_histories
 
239
  hypothetical_new_active_state = {"plot_id": plot_id_clicked, "type": action_type}
240
  is_toggling_off = current_active_action_from_state == hypothetical_new_active_state
241
 
 
242
  action_col_visible_update = gr.update(visible=False)
243
  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)
244
  formula_display_visible_update = gr.update(visible=False)
 
248
  new_active_action_state_to_set, new_current_chat_plot_id = None, current_chat_plot_id
249
  updated_chat_histories, new_explored_plot_id_to_set = current_chat_histories, current_explored_plot_id
250
 
 
251
  generated_panel_vis_updates = []
252
  generated_bomb_btn_updates = []
253
  generated_formula_btn_updates = []
 
256
 
257
 
258
  if is_toggling_off:
259
+ new_active_action_state_to_set = None
260
+ action_col_visible_update = gr.update(visible=False)
261
  logging.info(f"Toggling OFF panel {action_type} for {plot_id_clicked}.")
262
 
 
263
  for _ in plot_configs:
264
  generated_bomb_btn_updates.append(gr.update(value=BOMB_ICON))
265
  generated_formula_btn_updates.append(gr.update(value=FORMULA_ICON))
266
 
267
  if current_explored_plot_id:
 
268
  explored_cfg = next((p for p in plot_configs if p["id"] == current_explored_plot_id), None)
269
  explored_sec = explored_cfg["section"] if explored_cfg else None
270
  for i, sec_name in enumerate(unique_ordered_sections):
 
274
  generated_panel_vis_updates.append(gr.update(visible=is_exp))
275
  generated_explore_btn_updates.append(gr.update(value=ACTIVE_ICON if is_exp else EXPLORE_ICON))
276
  else:
 
277
  for i in range(num_unique_sections): section_title_vis_updates[i] = gr.update(visible=True)
278
  for _ in plot_configs:
279
  generated_panel_vis_updates.append(gr.update(visible=True))
280
+ generated_explore_btn_updates.append(gr.update(value=EXPLORE_ICON))
281
 
282
+ if action_type == "insights": new_current_chat_plot_id = None
283
 
284
  else: # Toggling ON a new action or switching actions
285
  new_active_action_state_to_set = hypothetical_new_active_state
286
+ action_col_visible_update = gr.update(visible=True)
287
+ new_explored_plot_id_to_set = None
288
  logging.info(f"Toggling ON panel {action_type} for {plot_id_clicked}. Cancelling explore view if any.")
289
 
 
290
  for i, sec_name in enumerate(unique_ordered_sections):
291
  section_title_vis_updates[i] = gr.update(visible=(sec_name == clicked_plot_section))
292
  for cfg in plot_configs:
293
  generated_panel_vis_updates.append(gr.update(visible=(cfg["id"] == plot_id_clicked)))
294
+ generated_explore_btn_updates.append(gr.update(value=EXPLORE_ICON))
295
 
 
296
  for cfg_btn in plot_configs:
297
  is_act_ins = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "insights"}
298
  is_act_for = new_active_action_state_to_set == {"plot_id": cfg_btn["id"], "type": "formula"}
 
304
  new_current_chat_plot_id = plot_id_clicked
305
  history = current_chat_histories.get(plot_id_clicked, [])
306
  summary = current_plot_data_for_chatbot.get(plot_id_clicked, f"No summary for '{clicked_plot_label}'.")
307
+ if not history:
308
  prompt, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
309
  llm_hist = [{"role": "user", "content": prompt}]
310
+ resp = await generate_llm_response(prompt, plot_id_clicked, clicked_plot_label, llm_hist, summary) # This is your existing LLM call
311
+ history = [{"role": "assistant", "content": resp}]
 
312
  updated_chat_histories = {**current_chat_histories, plot_id_clicked: history}
313
+ else:
314
+ _, sugg = get_initial_insight_prompt_and_suggestions(plot_id_clicked, clicked_plot_label, summary)
315
 
316
  chatbot_content_update = gr.update(value=history)
317
  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")
 
326
  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']])
327
  else: f_text += "(No detailed formula information found.)"
328
  formula_content_update = gr.update(value=f_text)
329
+ new_current_chat_plot_id = None
330
 
 
331
  final_updates = [
332
  action_col_visible_update, insights_chatbot_visible_update, chatbot_content_update,
333
  insights_chat_input_visible_update, insights_suggestions_row_visible_update,
334
  s1_upd, s2_upd, s3_upd, formula_display_visible_update, formula_content_update,
335
+ formula_close_hint_md, # This is the component for the hint's visibility, should be formula_close_hint_visible_update
 
336
  new_active_action_state_to_set, new_current_chat_plot_id, updated_chat_histories,
337
  new_explored_plot_id_to_set
338
  ]
339
+ # Correcting the hint update:
340
+ final_updates[10] = formula_close_hint_visible_update
341
+
342
+
343
+ final_updates.extend(generated_panel_vis_updates)
344
+ final_updates.extend(generated_bomb_btn_updates)
345
+ final_updates.extend(generated_formula_btn_updates)
346
+ final_updates.extend(generated_explore_btn_updates)
347
+ final_updates.extend(section_title_vis_updates)
348
 
349
+ logging.debug(f"handle_panel_action returning {len(final_updates)} updates. Expected {15 + 4*len(plot_configs) + num_unique_sections}.")
 
 
350
  return final_updates
351
 
352
  async def handle_chat_message_submission(user_message: str, current_plot_id: str, chat_histories: dict, current_plot_data_for_chatbot: dict ):
353
  if not current_plot_id or not user_message.strip():
 
354
  current_history_for_plot = chat_histories.get(current_plot_id, [])
355
  if not isinstance(current_history_for_plot, list): current_history_for_plot = []
356
  yield current_history_for_plot, gr.update(value=""), chat_histories; return
 
359
  lbl = cfg["label"] if cfg else "Selected Plot"
360
  summary = current_plot_data_for_chatbot.get(current_plot_id, f"No summary for '{lbl}'.")
361
 
 
362
  hist_for_plot = chat_histories.get(current_plot_id, [])
363
+ if not isinstance(hist_for_plot, list): hist_for_plot = []
364
 
365
  hist = hist_for_plot.copy() + [{"role": "user", "content": user_message}]
366
+ yield hist, gr.update(value=""), chat_histories
367
 
368
+ resp = await generate_llm_response(user_message, current_plot_id, lbl, hist, summary) # Existing LLM
 
369
  hist.append({"role": "assistant", "content": resp})
370
 
 
371
  updated_chat_histories = {**chat_histories, current_plot_id: hist}
372
  yield hist, "", updated_chat_histories
373
 
 
378
  if not isinstance(current_history_for_plot, list): current_history_for_plot = []
379
  yield current_history_for_plot, gr.update(value=""), chat_histories; return
380
 
 
381
  async for update_chunk in handle_chat_message_submission(suggestion_text, current_plot_id, chat_histories, current_plot_data_for_chatbot):
382
  yield update_chunk
383
 
384
 
385
  def handle_explore_click(plot_id_clicked, current_explored_plot_id_from_state, current_active_panel_action_state):
386
+ # This function remains synchronous as per original
387
  logging.info(f"Explore Click: Plot '{plot_id_clicked}'. Current Explored: {current_explored_plot_id_from_state}. Active Panel: {current_active_panel_action_state}")
388
  num_plots = len(plot_configs)
389
  if not plot_ui_objects:
390
  logging.error("plot_ui_objects not populated for handle_explore_click.")
 
391
  error_list_len = 4 + (4 * num_plots) + num_unique_sections
392
  error_list = [gr.update()] * error_list_len
393
+ error_list[0] = current_explored_plot_id_from_state
394
+ error_list[2] = current_active_panel_action_state
395
  return error_list
396
 
397
  new_explored_id_to_set = None
398
  is_toggling_off_explore = (plot_id_clicked == current_explored_plot_id_from_state)
399
 
400
+ action_col_upd = gr.update()
401
+ new_active_panel_state_upd = current_active_panel_action_state
402
+ formula_hint_upd = gr.update(visible=False)
403
 
404
  panel_vis_updates = []
405
  explore_btns_updates = []
 
411
  sec_of_clicked = clicked_cfg["section"] if clicked_cfg else None
412
 
413
  if is_toggling_off_explore:
414
+ new_explored_id_to_set = None
415
  logging.info(f"Stopping explore for {plot_id_clicked}. All plots/sections to be visible.")
416
  for i in range(num_unique_sections): section_title_vis_updates[i] = gr.update(visible=True)
417
  for _ in plot_configs:
418
  panel_vis_updates.append(gr.update(visible=True))
419
  explore_btns_updates.append(gr.update(value=EXPLORE_ICON))
420
+ bomb_btns_updates.append(gr.update())
421
+ formula_btns_updates.append(gr.update())
422
+ else:
 
 
423
  new_explored_id_to_set = plot_id_clicked
424
  logging.info(f"Exploring {plot_id_clicked}. Hiding other plots/sections.")
425
  for i, sec_name in enumerate(unique_ordered_sections):
 
429
  panel_vis_updates.append(gr.update(visible=is_target))
430
  explore_btns_updates.append(gr.update(value=ACTIVE_ICON if is_target else EXPLORE_ICON))
431
 
 
432
  if current_active_panel_action_state:
433
  logging.info("Closing active insight/formula panel due to explore click.")
434
+ action_col_upd = gr.update(visible=False)
435
+ new_active_panel_state_upd = None
436
+ formula_hint_upd = gr.update(visible=False)
 
437
  for _ in plot_configs:
438
  bomb_btns_updates.append(gr.update(value=BOMB_ICON))
439
  formula_btns_updates.append(gr.update(value=FORMULA_ICON))
440
  else:
 
441
  for _ in plot_configs:
442
  bomb_btns_updates.append(gr.update())
443
  formula_btns_updates.append(gr.update())
444
 
 
 
445
  final_explore_updates = [
446
  new_explored_id_to_set, action_col_upd, new_active_panel_state_upd, formula_hint_upd
447
  ]
 
455
  return final_explore_updates
456
 
457
 
 
 
458
  _base_action_panel_ui_outputs = [
459
+ global_actions_column_ui, insights_chatbot_ui, insights_chatbot_ui,
460
  insights_chat_input_ui, insights_suggestions_row_ui,
461
  insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn,
462
+ formula_display_markdown_ui, formula_display_markdown_ui,
463
+ formula_close_hint_md
464
  ]
 
465
  _action_panel_state_outputs = [active_panel_action_state, current_chat_plot_id_st, chat_histories_st, explored_plot_id_state]
466
 
467
+ action_panel_outputs_list = _base_action_panel_ui_outputs + _action_panel_state_outputs
468
+ action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("panel_component", gr.update()) for pc in plot_configs])
469
+ action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("bomb_button", gr.update()) for pc in plot_configs])
470
+ action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs])
471
+ action_panel_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("explore_button", gr.update()) for pc in plot_configs])
472
+ action_panel_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections])
473
+
 
 
474
  _explore_base_outputs = [explored_plot_id_state, global_actions_column_ui, active_panel_action_state, formula_close_hint_md]
475
+ explore_outputs_list = _explore_base_outputs
476
+ explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("panel_component", gr.update()) for pc in plot_configs])
477
+ explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("explore_button", gr.update()) for pc in plot_configs])
478
+ explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("bomb_button", gr.update()) for pc in plot_configs])
479
+ explore_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("formula_button", gr.update()) for pc in plot_configs])
480
+ explore_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections])
 
481
 
482
  action_click_inputs = [active_panel_action_state, chat_histories_st, current_chat_plot_id_st, plot_data_for_chatbot_st, explored_plot_id_state]
483
  explore_click_inputs = [explored_plot_id_state, active_panel_action_state]
 
491
  plot_id = config_item["id"]
492
  if plot_id in plot_ui_objects:
493
  ui_obj = plot_ui_objects[plot_id]
 
494
  if ui_obj.get("bomb_button"):
495
  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}")
496
  if ui_obj.get("formula_button"):
497
  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}")
498
  if ui_obj.get("explore_button"):
499
+ # Original lambda was not async, ensure it matches handle_explore_click signature and type
500
+ ui_obj["explore_button"].click(
501
+ 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),
502
+ inputs=explore_click_inputs,
503
+ outputs=explore_outputs_list,
504
+ api_name=f"action_explore_{plot_id}"
505
+ ) # if handle_explore_click becomes async, this needs 'await' or be wrapped
506
  else: logging.warning(f"UI object for plot_id '{plot_id}' not found for click handlers.")
507
 
508
 
 
516
  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")
517
 
518
 
519
+ def refresh_all_analytics_ui_elements(current_token_state_val, date_filter_val, custom_start_val, custom_end_val, current_chat_histories_val):
520
+ # This function remains synchronous as per original
521
  logging.info("Refreshing all analytics UI elements and resetting actions/chat.")
522
+ plot_gen_results = update_analytics_plots_figures(current_token_state_val, date_filter_val, custom_start_val, custom_end_val, plot_configs)
523
  status_msg, gen_figs, new_summaries = plot_gen_results[0], plot_gen_results[1:-1], plot_gen_results[-1]
524
 
525
+ all_updates = [status_msg]
526
+ 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))])
527
 
 
528
  all_updates.extend([
529
+ gr.update(visible=False),
530
+ gr.update(value=[], visible=False),
531
+ gr.update(value="", visible=False),
532
+ gr.update(visible=False),
533
+ gr.update(value="S1"), gr.update(value="S2"), gr.update(value="S3"),
534
+ gr.update(value="Formula details here.", visible=False),
535
+ gr.update(visible=False)
536
  ])
537
 
 
538
  all_updates.extend([
539
+ None,
540
+ None,
541
+ {},
542
+ new_summaries
543
  ])
544
 
 
545
  for _ in plot_configs:
546
  all_updates.extend([
547
  gr.update(value=BOMB_ICON),
548
  gr.update(value=FORMULA_ICON),
549
  gr.update(value=EXPLORE_ICON),
550
+ gr.update(visible=True)
551
  ])
552
 
553
+ all_updates.append(None)
554
+ all_updates.extend([gr.update(visible=True)] * num_unique_sections)
555
 
 
 
 
556
  logging.info(f"Prepared {len(all_updates)} updates for analytics refresh. Expected {15 + 5*len(plot_configs) + num_unique_sections}.")
557
  return all_updates
558
 
559
+ apply_filter_and_sync_outputs_list = [analytics_status_md]
560
+ apply_filter_and_sync_outputs_list.extend([plot_ui_objects.get(pc["id"], {}).get("plot_component", gr.update()) for pc in plot_configs])
 
561
 
 
562
  _ui_resets_for_filter = [
563
  global_actions_column_ui, insights_chatbot_ui, insights_chat_input_ui,
564
  insights_suggestions_row_ui, insights_suggestion_1_btn, insights_suggestion_2_btn, insights_suggestion_3_btn,
 
566
  ]
567
  apply_filter_and_sync_outputs_list.extend(_ui_resets_for_filter)
568
 
 
569
  _state_resets_for_filter = [active_panel_action_state, current_chat_plot_id_st, chat_histories_st, plot_data_for_chatbot_st]
570
  apply_filter_and_sync_outputs_list.extend(_state_resets_for_filter)
571
 
 
572
  for pc in plot_configs:
573
  pid = pc["id"]
574
  apply_filter_and_sync_outputs_list.extend([
575
  plot_ui_objects.get(pid, {}).get("bomb_button", gr.update()),
576
  plot_ui_objects.get(pid, {}).get("formula_button", gr.update()),
577
  plot_ui_objects.get(pid, {}).get("explore_button", gr.update()),
578
+ plot_ui_objects.get(pid, {}).get("panel_component", gr.update())
579
  ])
580
 
 
581
  apply_filter_and_sync_outputs_list.append(explored_plot_id_state)
582
 
 
583
  apply_filter_and_sync_outputs_list.extend([section_titles_map.get(s_name, gr.update()) for s_name in unique_ordered_sections])
 
584
 
585
  apply_filter_btn.click(
586
  fn=refresh_all_analytics_ui_elements,
 
612
  outputs=[follower_stats_html, fs_plot_monthly_gains, fs_plot_seniority, fs_plot_industry],
613
  show_progress="full"
614
  )
615
+
616
+ # --- NEW: Tab 5 for Employer Branding Agent ---
617
+ with gr.TabItem("5️⃣ Agente AI Employer Branding", id="tab_eb_agent"):
618
+ gr.Markdown("## 🤖 Interagisci con l'Agente AI per l'Employer Branding")
619
 
620
+ if not EB_AGENT_AVAILABLE:
621
+ gr.Markdown("<p style='color:red;font-weight:bold;'>Attenzione: Il modulo dell'Agente AI per l'Employer Branding non è stato caricato correttamente. Controllare i log e l'installazione della libreria `google-generativeai` e la variabile d'ambiente `GEMINI_API_KEY`.</p>")
622
+ elif not os.getenv('GEMINI_API_KEY'):
623
+ gr.Markdown("<p style='color:orange;font-weight:bold;'>Attenzione: La variabile d'ambiente `GEMINI_API_KEY` non è impostata. Le funzionalità dell'Agente AI saranno limitate o non funzioneranno.</p>")
624
+
625
+
626
+ gr.Markdown(
627
+ "Fai domande sui tuoi dati LinkedIn (statistiche follower, post e menzioni) per ottenere insights e codice Pandas per analizzarli. "
628
+ "L'agente utilizza i dati attualmente disponibili nello stato dell'applicazione."
629
+ )
630
+ with gr.Row():
631
+ with gr.Column(scale=2):
632
+ eb_agent_chatbot_ui = gr.Chatbot(
633
+ label="Chat con Agente AI EB",
634
+ value=[[None, "Ciao! Sono il tuo Agente AI per l'Employer Branding. Come posso aiutarti ad analizzare i tuoi dati LinkedIn oggi? Chiedimi di generare codice Pandas o di fornire insights."]] if EB_AGENT_AVAILABLE else [[None, "Agente AI non disponibile."]],
635
+ bubble_full_width=False,
636
+ height=500,
637
+ placeholder="L'Agente AI è pronto. Chiedi pure..."
638
+ )
639
+ eb_agent_chat_input_ui = gr.Textbox(
640
+ label="La tua domanda:",
641
+ placeholder="Es: 'Mostrami le aziende dei miei follower nel settore tecnologico' o 'Qual è il sentiment medio delle mie menzioni?'",
642
+ lines=3,
643
+ interactive=EB_AGENT_AVAILABLE # Disable if agent not available
644
+ )
645
+ with gr.Row():
646
+ eb_agent_submit_btn = gr.Button("💬 Invia Messaggio", variant="primary", interactive=EB_AGENT_AVAILABLE)
647
+ eb_agent_clear_btn = gr.Button("🗑️ Cancella Chat", variant="stop", interactive=EB_AGENT_AVAILABLE)
648
+ with gr.Column(scale=1):
649
+ gr.Markdown("#### Schemi Dati Disponibili per l'Agente:")
650
+ eb_agent_schema_display_md = gr.Markdown("Gli schemi dei dati (follower, post, menzioni) verranno mostrati qui quando l'agente viene inizializzato con una query.")
651
+ eb_agent_status_md = gr.Markdown("Stato Agente: In attesa di input...")
652
+
653
+ # --- NEW: Handler for Employer Branding Agent Chat ---
654
+ eb_agent_instance_dict = {"agent": None} # To store agent instance across calls if needed, or re-init
655
+
656
+ async def handle_eb_agent_chat(user_message: str, chat_history_list: list, current_token_state: dict):
657
+ if not EB_AGENT_AVAILABLE or not os.getenv('GEMINI_API_KEY'):
658
+ no_key_msg = "L'Agente AI non è disponibile. Assicurati che GEMINI_API_KEY sia configurata."
659
+ chat_history_list.append([user_message, no_key_msg])
660
+ return chat_history_list, chat_history_list, "", gr.update(value=no_key_msg)
661
+
662
+ if not user_message.strip():
663
+ return chat_history_list, chat_history_list, "", gr.update(value="Stato Agente: Per favore, inserisci una domanda.")
664
+
665
+ status_update_msg = "Stato Agente: Elaborazione della tua richiesta..."
666
+ yield chat_history_list + [[user_message, None]], chat_history_list + [[user_message, None]], "", gr.update(value=status_update_msg) # Show user message immediately
667
+
668
+ # Prepare DataFrames for the agent
669
+ df_follower_stats = current_token_state.get("bubble_follower_stats_df", pd.DataFrame())
670
+ df_posts = current_token_state.get("bubble_posts_df", pd.DataFrame()) # Contains post content
671
+ df_post_stats = current_token_state.get("bubble_post_stats_df", pd.DataFrame()) # Contains post metrics
672
+ df_mentions = current_token_state.get("bubble_mentions_df", pd.DataFrame())
673
+
674
+ # For simplicity, we can merge posts and post_stats if they have a common key (e.g., post_id)
675
+ # Assuming 'id' in df_posts corresponds to 'post_id' in df_post_stats
676
+ # This is a guess, adjust based on your actual schemas.
677
+ # If no common key, pass them separately and instruct LLM.
678
+ df_posts_combined = df_posts
679
+ if not df_posts.empty and not df_post_stats.empty:
680
+ # Attempt merge, ensure column names are correct for your DFs
681
+ # Example: common_col_posts = 'id'; common_col_stats = 'post_id'
682
+ # For now, let's assume they might not be easily mergeable or LLM can handle separately
683
+ pass # LLM will see df_posts and df_post_stats separately
684
+
685
+
686
+ dataframes_for_agent = {
687
+ "follower_stats": df_follower_stats.copy() if not df_follower_stats.empty else pd.DataFrame(columns=['no_data_follower_stats']), # Use copy to avoid modifying state
688
+ "posts": df_posts.copy() if not df_posts.empty else pd.DataFrame(columns=['no_data_posts']),
689
+ "post_stats": df_post_stats.copy() if not df_post_stats.empty else pd.DataFrame(columns=['no_data_post_stats']),
690
+ "mentions": df_mentions.copy() if not df_mentions.empty else pd.DataFrame(columns=['no_data_mentions'])
691
+ }
692
+
693
+ # Display schemas for user reference
694
+ schemas_text = "Schemi DataFrames inviati all'Agente:\n\n"
695
+ from eb_agent_module import get_all_schemas_representation # Re-import for safety if run in different context
696
+ schemas_text += get_all_schemas_representation(dataframes_for_agent)
697
+ # Limit schema display length for UI
698
+ max_schema_display_len = 1500
699
+ if len(schemas_text) > max_schema_display_len:
700
+ schemas_text = schemas_text[:max_schema_display_len] + "\n...(schemi troncati per la visualizzazione)"
701
+
702
+ # Initialize or update agent
703
+ # For simplicity, re-initialize. If RAG embeddings are slow, consider stateful agent.
704
+ current_agent = EmployerBrandingAgent(
705
+ llm_model_name=EB_AGENT_LLM_MODEL,
706
+ generation_config_params=EB_AGENT_GEN_CONFIG,
707
+ safety_settings=EB_AGENT_SAFETY_SETTINGS,
708
+ all_dataframes=dataframes_for_agent,
709
+ rag_documents_df=eb_agent_default_rag_docs.copy(), # Use a copy of default RAG docs
710
+ embedding_model_name=EB_AGENT_EMBEDDING_MODEL,
711
+ force_sandbox=True # True to get Python code
712
+ )
713
+ # Restore chat history to the agent if it were stateful
714
+ # current_agent.chat_history = [msg for pair in chat_history_list for msg_type, msg_content in (('user', pair[0]), ('assistant', pair[1])) if msg_content is not None]
715
+ # For stateless re-init, we pass history to process_query if needed by LLM, but agent itself starts fresh or manages its own history via its methods
716
+
717
+ # Simplified history for this agent: it manages its own via process_query
718
+ # We only need to pass the user_message.
719
+ # The agent's process_query will handle its internal history.
720
+
721
+ # If the agent's internal history needs to match the UI history:
722
+ # Convert Gradio chat history to agent's expected format
723
+ agent_internal_history = []
724
+ for user_q, ai_r in chat_history_list:
725
+ if user_q: agent_internal_history.append({"role": "user", "content": user_q})
726
+ if ai_r: agent_internal_history.append({"role": "assistant", "content": ai_r}) # or "model"
727
+
728
+ # Update agent's history before processing (if agent supports this way)
729
+ current_agent.chat_history = agent_internal_history # Agent will append new query and response
730
+
731
+ try:
732
+ logging.info(f"Sending to EB Agent. User: '{user_message}'. DF Keys: {list(dataframes_for_agent.keys())}")
733
+ # The agent's process_query already appends to its internal history
734
+ ai_response = await current_agent.process_query(user_query=user_message)
735
+
736
+ # The agent's internal history is now updated. We need to reflect this in Gradio's history state.
737
+ # The last two entries in agent.chat_history are the current user_message and ai_response
738
+ updated_gradio_history = []
739
+ temp_hist = current_agent.chat_history
740
+ for i in range(0, len(temp_hist), 2):
741
+ u_msg = temp_hist[i]['content']
742
+ a_msg = temp_hist[i+1]['content'] if i+1 < len(temp_hist) else "Thinking..." # Should have assistant response
743
+ updated_gradio_history.append([u_msg, a_msg])
744
+
745
+ status_update_msg = "Stato Agente: Risposta ricevuta."
746
+ yield updated_gradio_history, updated_gradio_history, "", gr.update(value=status_update_msg), gr.update(value=schemas_text)
747
+
748
+ except Exception as e:
749
+ logging.error(f"Error during EB Agent processing: {e}", exc_info=True)
750
+ error_msg = f"# Errore dell'Agente AI:\n{type(e).__name__}: {str(e)}"
751
+ # Use the last known good history before error for UI state
752
+ chat_history_list.append([user_message, error_msg]) # Add error to current turn
753
+ status_update_msg = f"Stato Agente: Errore - {type(e).__name__}"
754
+ yield chat_history_list, chat_history_list, "", gr.update(value=status_update_msg), gr.update(value=schemas_text)
755
+
756
+
757
+ def clear_eb_agent_chat_history():
758
+ # eb_agent_instance_dict["agent"] might need its history cleared too if stateful
759
+ # For re-init agent, just clearing UI state is enough.
760
+ # If agent instance is stored and reused:
761
+ # agent_to_clear = eb_agent_instance_dict.get("agent")
762
+ # if agent_to_clear and hasattr(agent_to_clear, 'clear_chat_history'):
763
+ # agent_to_clear.clear_chat_history()
764
+ initial_msg = "Ciao! Sono il tuo Agente AI per l'Employer Branding. Come posso aiutarti?" if EB_AGENT_AVAILABLE else "Agente AI non disponibile."
765
+ return [[None, initial_msg]], [[None, initial_msg]], "Stato Agente: Chat resettata."
766
+
767
+ # Connect UI to Handler for EB Agent
768
+ eb_agent_submit_btn.click(
769
+ fn=handle_eb_agent_chat,
770
+ inputs=[eb_agent_chat_input_ui, eb_agent_chat_history_st, token_state],
771
+ outputs=[eb_agent_chatbot_ui, eb_agent_chat_history_st, eb_agent_chat_input_ui, eb_agent_status_md, eb_agent_schema_display_md],
772
+ api_name="eb_agent_chat_submit"
773
+ )
774
+ eb_agent_chat_input_ui.submit(
775
+ fn=handle_eb_agent_chat,
776
+ inputs=[eb_agent_chat_input_ui, eb_agent_chat_history_st, token_state],
777
+ outputs=[eb_agent_chatbot_ui, eb_agent_chat_history_st, eb_agent_chat_input_ui, eb_agent_status_md, eb_agent_schema_display_md],
778
+ api_name="eb_agent_chat_enter"
779
+ )
780
+ eb_agent_clear_btn.click(
781
+ fn=clear_eb_agent_chat_history,
782
+ inputs=[],
783
+ outputs=[eb_agent_chatbot_ui, eb_agent_chat_history_st, eb_agent_status_md],
784
+ api_name="eb_agent_clear_chat"
785
+ )
786
+
787
+
788
+ # --- Sync Events (at the end of the app's 'with gr.Blocks()' context) ---
789
  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")
790
  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)
791
  sync_event_part3 = sync_event_part2.then(fn=display_main_dashboard, inputs=[token_state], outputs=[dashboard_display_html], show_progress=False)
792
+ sync_event_final = sync_event_part3.then(
793
+ fn=refresh_all_analytics_ui_elements, # This is synchronous
794
+ inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker, chat_histories_st],
795
+ outputs=apply_filter_and_sync_outputs_list,
796
+ show_progress="full"
797
+ )
798
 
799
 
800
  if __name__ == "__main__":
801
  if not os.environ.get(LINKEDIN_CLIENT_ID_ENV_VAR): logging.warning(f"ATTENZIONE: '{LINKEDIN_CLIENT_ID_ENV_VAR}' non impostata.")
802
  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]):
803
  logging.warning("ATTENZIONE: Variabili Bubble non impostate.")
804
+
805
+ if not EB_AGENT_AVAILABLE:
806
+ logging.error("L'Agente AI per l'Employer Branding non è disponibile a causa di errori di importazione.")
807
+ elif not os.getenv('GEMINI_API_KEY'):
808
+ logging.warning("ATTENZIONE: GEMINI_API_KEY non è impostata. L'Agente AI per l'Employer Branding potrebbe non funzionare.")
809
+
810
  try: logging.info(f"Matplotlib: {matplotlib.__version__}, Backend: {matplotlib.get_backend()}")
811
  except ImportError: logging.warning("Matplotlib non trovato.")
812
+
813
+ app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
814
+