Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -5,6 +5,7 @@ import logging
|
|
5 |
import matplotlib
|
6 |
matplotlib.use('Agg') # Set backend for Matplotlib to avoid GUI conflicts with Gradio
|
7 |
import matplotlib.pyplot as plt
|
|
|
8 |
|
9 |
# --- Module Imports ---
|
10 |
from gradio_utils import get_url_user_token
|
@@ -34,7 +35,7 @@ from analytics_plot_generator import (
|
|
34 |
generate_engagement_rate_over_time_plot,
|
35 |
generate_reach_over_time_plot,
|
36 |
generate_impressions_over_time_plot,
|
37 |
-
create_placeholder_plot,
|
38 |
generate_likes_over_time_plot,
|
39 |
generate_clicks_over_time_plot,
|
40 |
generate_shares_over_time_plot,
|
@@ -51,7 +52,7 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(
|
|
51 |
# --- Analytics Tab: Plot Figure Generation Function ---
|
52 |
def update_analytics_plots_figures(token_state_value, date_filter_option, custom_start_date, custom_end_date):
|
53 |
logging.info(f"Updating analytics plot figures. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
|
54 |
-
num_expected_plots = 23
|
55 |
|
56 |
if not token_state_value or not token_state_value.get("token"):
|
57 |
message = "❌ Access denied. No token. Cannot generate analytics."
|
@@ -60,6 +61,8 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
60 |
return [message] + placeholder_figs
|
61 |
|
62 |
try:
|
|
|
|
|
63 |
(filtered_merged_posts_df,
|
64 |
filtered_mentions_df,
|
65 |
date_filtered_follower_stats_df,
|
@@ -68,6 +71,7 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
68 |
prepare_filtered_analytics_data(
|
69 |
token_state_value, date_filter_option, custom_start_date, custom_end_date
|
70 |
)
|
|
|
71 |
except Exception as e:
|
72 |
error_msg = f"❌ Error preparing analytics data: {e}"
|
73 |
logging.error(error_msg, exc_info=True)
|
@@ -80,8 +84,17 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
80 |
eb_labels_col_name = token_state_value.get("config_eb_labels_col", "eb_labels")
|
81 |
|
82 |
plot_figs = []
|
|
|
|
|
|
|
83 |
try:
|
|
|
|
|
|
|
84 |
plot_figs.append(generate_posts_activity_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
|
|
|
|
|
|
85 |
plot_figs.append(generate_engagement_type_plot(filtered_merged_posts_df))
|
86 |
fig_mentions_activity_shared = generate_mentions_activity_plot(filtered_mentions_df, date_column=date_column_mentions)
|
87 |
fig_mention_sentiment_shared = generate_mention_sentiment_plot(filtered_mentions_df)
|
@@ -94,7 +107,7 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
94 |
plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_industry', plot_title="Followers by Industry"))
|
95 |
plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_seniority', plot_title="Followers by Seniority"))
|
96 |
plot_figs.append(generate_engagement_rate_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
97 |
-
plot_figs.append(generate_reach_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
98 |
plot_figs.append(generate_impressions_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
99 |
plot_figs.append(generate_likes_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
100 |
plot_figs.append(generate_clicks_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
@@ -104,27 +117,30 @@ def update_analytics_plots_figures(token_state_value, date_filter_option, custom
|
|
104 |
plot_figs.append(generate_post_frequency_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
105 |
plot_figs.append(generate_content_format_breakdown_plot(filtered_merged_posts_df, format_col=media_type_col_name))
|
106 |
plot_figs.append(generate_content_topic_breakdown_plot(filtered_merged_posts_df, topics_col=eb_labels_col_name))
|
107 |
-
plot_figs.append(fig_mentions_activity_shared)
|
108 |
-
plot_figs.append(fig_mention_sentiment_shared)
|
|
|
|
|
|
|
109 |
|
110 |
message = f"📊 Analytics updated for period: {date_filter_option}"
|
111 |
if date_filter_option == "Custom Range":
|
112 |
s_display = start_dt_for_msg.strftime('%Y-%m-%d') if start_dt_for_msg else "Any"
|
113 |
e_display = end_dt_for_msg.strftime('%Y-%m-%d') if end_dt_for_msg else "Any"
|
114 |
message += f" (From: {s_display} To: {e_display})"
|
115 |
-
|
116 |
final_plot_figs = []
|
117 |
for i, p_fig in enumerate(plot_figs):
|
118 |
-
if p_fig is not None and not isinstance(p_fig, str):
|
119 |
final_plot_figs.append(p_fig)
|
120 |
else:
|
121 |
logging.warning(f"Plot figure generation failed or returned unexpected type for slot {i}, using placeholder. Figure: {p_fig}")
|
122 |
final_plot_figs.append(create_placeholder_plot(title="Plot Error", message="Failed to generate this plot figure."))
|
123 |
-
|
124 |
while len(final_plot_figs) < num_expected_plots:
|
125 |
logging.warning(f"Padding missing plot figure. Expected {num_expected_plots}, got {len(final_plot_figs)}.")
|
126 |
final_plot_figs.append(create_placeholder_plot(title="Missing Plot", message="Plot figure could not be generated."))
|
127 |
-
|
128 |
return [message] + final_plot_figs[:num_expected_plots]
|
129 |
|
130 |
except Exception as e:
|
@@ -173,22 +189,22 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
173 |
outputs=[status_box, token_state, sync_data_btn, dashboard_display_html],
|
174 |
show_progress="full"
|
175 |
)
|
176 |
-
|
177 |
with gr.TabItem("2️⃣ Analytics", id="tab_analytics"):
|
178 |
gr.Markdown("## 📈 LinkedIn Performance Analytics")
|
179 |
gr.Markdown("Select a date range. Click buttons for actions.")
|
180 |
-
|
181 |
analytics_status_md = gr.Markdown("Analytics status...")
|
182 |
|
183 |
-
with gr.Row():
|
184 |
date_filter_selector = gr.Radio(
|
185 |
["All Time", "Last 7 Days", "Last 30 Days", "Custom Range"],
|
186 |
label="Select Date Range", value="Last 30 Days", scale=3
|
187 |
)
|
188 |
with gr.Column(scale=2):
|
189 |
-
custom_start_date_picker = gr.DateTime(label="Start Date", visible=False, include_time=False, type="datetime")
|
190 |
-
custom_end_date_picker = gr.DateTime(label="End Date", visible=False, include_time=False, type="datetime")
|
191 |
-
|
192 |
apply_filter_btn = gr.Button("🔍 Apply Filter & Refresh Analytics", variant="primary")
|
193 |
|
194 |
def toggle_custom_date_pickers(selection):
|
@@ -204,8 +220,8 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
204 |
plot_configs = [
|
205 |
{"label": "Posts Activity Over Time", "id": "posts_activity", "section": "Posts & Engagement Overview"},
|
206 |
{"label": "Post Engagement Types", "id": "engagement_type", "section": "Posts & Engagement Overview"},
|
207 |
-
{"label": "Mentions Activity Over Time", "id": "mentions_activity", "section": "Mentions Overview"},
|
208 |
-
{"label": "Mention Sentiment Distribution", "id": "mention_sentiment", "section": "Mentions Overview"},
|
209 |
{"label": "Followers Count Over Time", "id": "followers_count", "section": "Follower Dynamics"},
|
210 |
{"label": "Followers Growth Rate", "id": "followers_growth_rate", "section": "Follower Dynamics"},
|
211 |
{"label": "Followers by Location", "id": "followers_by_location", "section": "Follower Demographics"},
|
@@ -213,7 +229,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
213 |
{"label": "Followers by Industry", "id": "followers_by_industry", "section": "Follower Demographics"},
|
214 |
{"label": "Followers by Seniority", "id": "followers_by_seniority", "section": "Follower Demographics"},
|
215 |
{"label": "Engagement Rate Over Time", "id": "engagement_rate", "section": "Post Performance Insights"},
|
216 |
-
{"label": "Reach Over Time
|
217 |
{"label": "Impressions Over Time", "id": "impressions_over_time", "section": "Post Performance Insights"},
|
218 |
{"label": "Reactions (Likes) Over Time", "id": "likes_over_time", "section": "Post Performance Insights"},
|
219 |
{"label": "Clicks Over Time", "id": "clicks_over_time", "section": "Detailed Post Engagement Over Time"},
|
@@ -223,218 +239,196 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
223 |
{"label": "Post Frequency", "id": "post_frequency_cs", "section": "Content Strategy Analysis"},
|
224 |
{"label": "Breakdown of Content by Format", "id": "content_format_breakdown_cs", "section": "Content Strategy Analysis"},
|
225 |
{"label": "Breakdown of Content by Topics", "id": "content_topic_breakdown_cs", "section": "Content Strategy Analysis"},
|
226 |
-
{"label": "Mentions Volume Over Time (Detailed)", "id": "mention_analysis_volume", "section": "Mention Analysis (Detailed)"},
|
227 |
-
{"label": "Breakdown of Mentions by Sentiment (Detailed)", "id": "mention_analysis_sentiment", "section": "Mention Analysis (Detailed)"}
|
228 |
]
|
229 |
assert len(plot_configs) == 23, "Mismatch in plot_configs and expected plots."
|
230 |
|
231 |
-
|
232 |
-
# Stores
|
233 |
-
|
234 |
-
|
235 |
-
explored_plot_id_state = gr.State(None)
|
236 |
|
237 |
-
with gr.Row(equal_height=False):
|
238 |
-
with gr.Column(scale=8) as plots_area_col:
|
|
|
239 |
plot_ui_objects = build_analytics_tab_plot_area(plot_configs)
|
240 |
-
|
241 |
-
with gr.Column(scale=4, visible=False) as global_actions_column_ui:
|
242 |
-
gr.Markdown("### 💡 Generated Content")
|
243 |
global_actions_markdown_ui = gr.Markdown("Click a button (💣, ƒ) on a plot to see content here.")
|
244 |
|
245 |
|
246 |
# --- Event Handler for Insights and Formula Buttons ---
|
247 |
-
def handle_panel_action(plot_id_clicked, action_type,
|
248 |
-
logging.info(f"Action '{action_type}' for plot: {plot_id_clicked}. Current active: {
|
249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
250 |
clicked_plot_label = plot_ui_objects.get(plot_id_clicked, {}).get("label", "Selected Plot")
|
251 |
|
252 |
-
|
253 |
-
|
254 |
-
is_toggling_off = current_active_action == new_active_action_state
|
255 |
|
|
|
256 |
content_text = ""
|
257 |
action_col_visible = False
|
258 |
-
|
259 |
-
# Button icon updates - prepare a list of gr.update for all buttons
|
260 |
-
button_updates = {} # Dict to hold updates for gr.Button objects
|
261 |
|
262 |
if is_toggling_off:
|
263 |
-
|
264 |
-
content_text = f"{action_type.capitalize()} for {clicked_plot_label}
|
265 |
action_col_visible = False
|
266 |
logging.info(f"Closing {action_type} panel for {plot_id_clicked}")
|
267 |
-
# Reset the clicked button to its original icon
|
268 |
-
if action_type == "insights":
|
269 |
-
button_updates[plot_ui_objects[plot_id_clicked]["bomb_button"]] = gr.update(value=BOMB_ICON)
|
270 |
-
elif action_type == "formula":
|
271 |
-
button_updates[plot_ui_objects[plot_id_clicked]["formula_button"]] = gr.update(value=FORMULA_ICON)
|
272 |
else: # Activating or switching
|
|
|
273 |
action_col_visible = True
|
274 |
if action_type == "insights":
|
275 |
-
# TODO: Implement actual insight generation
|
276 |
-
content_text = f"**Insights for: {clicked_plot_label}**\n\nPlot ID: `{plot_id_clicked}`.\
|
277 |
-
button_updates[plot_ui_objects[plot_id_clicked]["bomb_button"]] = gr.update(value=ACTIVE_ICON)
|
278 |
elif action_type == "formula":
|
279 |
# TODO: Implement actual formula display
|
280 |
-
content_text = f"**Formula/Methodology for: {clicked_plot_label}**\n\nPlot ID: `{plot_id_clicked}`.\
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
295 |
|
296 |
-
# Construct the list of updates for Gradio
|
297 |
-
# Order: global_actions_column_ui, global_actions_markdown_ui, active_panel_action_state, then all button updates
|
298 |
final_updates = [
|
299 |
-
gr.update(visible=action_col_visible),
|
300 |
-
gr.update(value=content_text),
|
301 |
-
|
302 |
-
]
|
303 |
-
# Add button updates in a consistent order (e.g., order of plot_configs)
|
304 |
-
for cfg in plot_configs:
|
305 |
-
p_id = cfg["id"]
|
306 |
-
if p_id in plot_ui_objects: # Check if plot_id exists in UI objects
|
307 |
-
if plot_ui_objects[p_id]["bomb_button"] in button_updates:
|
308 |
-
final_updates.append(button_updates[plot_ui_objects[p_id]["bomb_button"]])
|
309 |
-
else:
|
310 |
-
final_updates.append(gr.update(value=BOMB_ICON))
|
311 |
-
|
312 |
-
if plot_ui_objects[p_id]["formula_button"] in button_updates:
|
313 |
-
final_updates.append(button_updates[plot_ui_objects[p_id]["formula_button"]])
|
314 |
-
else:
|
315 |
-
final_updates.append(gr.update(value=FORMULA_ICON))
|
316 |
-
else: # Should not happen if plot_ui_objects is complete
|
317 |
-
final_updates.extend([gr.update(), gr.update()]) # Add empty updates to maintain length
|
318 |
|
319 |
return final_updates
|
320 |
|
321 |
# --- Event Handler for Explore Button ---
|
322 |
-
def handle_explore_click(plot_id_clicked,
|
323 |
-
logging.info(f"Explore clicked for: {plot_id_clicked}. Currently explored: {
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
|
329 |
-
|
|
|
|
|
330 |
|
331 |
if is_toggling_off:
|
332 |
-
|
333 |
-
button_icon_updates[plot_ui_objects[plot_id_clicked]["explore_button"]] = gr.update(value=EXPLORE_ICON)
|
334 |
logging.info(f"Un-exploring plot: {plot_id_clicked}")
|
335 |
else:
|
336 |
-
|
337 |
-
button_icon_updates[plot_ui_objects[plot_id_clicked]["explore_button"]] = gr.update(value=ACTIVE_ICON)
|
338 |
-
if current_explored_plot_id and current_explored_plot_id != plot_id_clicked:
|
339 |
-
button_icon_updates[plot_ui_objects[current_explored_plot_id]["explore_button"]] = gr.update(value=EXPLORE_ICON)
|
340 |
logging.info(f"Exploring plot: {plot_id_clicked}")
|
341 |
|
342 |
-
for
|
343 |
-
|
344 |
-
if config1_id not in plot_ui_objects: continue # Skip if panel doesn't exist
|
345 |
-
panel1 = plot_ui_objects[config1_id]["panel_component"]
|
346 |
-
|
347 |
-
show_panel1 = not new_explored_id or (config1_id == new_explored_id)
|
348 |
-
panel_visibility_updates[panel1] = gr.update(visible=show_panel1)
|
349 |
-
|
350 |
-
if i + 1 < len(plot_configs):
|
351 |
-
config2_id = plot_configs[i+1]["id"]
|
352 |
-
if config2_id not in plot_ui_objects: continue # Skip if panel doesn't exist
|
353 |
-
|
354 |
-
if plot_configs[i+1]["section"] == plot_configs[i]["section"]:
|
355 |
-
panel2 = plot_ui_objects[config2_id]["panel_component"]
|
356 |
-
show_panel2 = not new_explored_id or (config2_id == new_explored_id)
|
357 |
-
|
358 |
-
if new_explored_id: # If a plot is being explored
|
359 |
-
if config1_id == new_explored_id: show_panel2 = False # Hide sibling if first is explored
|
360 |
-
elif config2_id == new_explored_id: show_panel1 = False # Hide sibling if second is explored
|
361 |
-
|
362 |
-
panel_visibility_updates[panel1] = gr.update(visible=show_panel1) # Re-update panel1 if changed
|
363 |
-
panel_visibility_updates[panel2] = gr.update(visible=show_panel2)
|
364 |
-
|
365 |
-
final_updates = [new_explored_id]
|
366 |
for cfg in plot_configs:
|
367 |
p_id = cfg["id"]
|
368 |
if p_id in plot_ui_objects:
|
369 |
-
|
370 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
371 |
|
372 |
-
|
373 |
-
|
|
|
|
|
|
|
374 |
else: # Should not happen
|
375 |
-
|
376 |
|
|
|
377 |
return final_updates
|
378 |
|
379 |
-
# ---
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
|
|
|
|
|
|
|
|
384 |
]
|
385 |
-
for
|
386 |
-
|
387 |
-
if
|
388 |
-
|
389 |
-
|
390 |
-
else: #
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
|
|
401 |
|
402 |
|
|
|
|
|
|
|
|
|
|
|
403 |
for config_item in plot_configs:
|
404 |
plot_id = config_item["id"]
|
405 |
-
if plot_id in plot_ui_objects: # Ensure component exists
|
406 |
ui_obj = plot_ui_objects[plot_id]
|
407 |
-
|
408 |
-
current_action_outputs = [
|
409 |
-
global_actions_column_ui,
|
410 |
-
global_actions_markdown_ui,
|
411 |
-
active_panel_action_state
|
412 |
-
] + [plot_ui_objects[c["id"]]["bomb_button"] for c in plot_configs if c["id"] in plot_ui_objects] \
|
413 |
-
+ [plot_ui_objects[c["id"]]["formula_button"] for c in plot_configs if c["id"] in plot_ui_objects]
|
414 |
-
|
415 |
-
current_explore_outputs = [explored_plot_id_state] \
|
416 |
-
+ [plot_ui_objects[c["id"]]["panel_component"] for c in plot_configs if c["id"] in plot_ui_objects] \
|
417 |
-
+ [plot_ui_objects[c["id"]]["explore_button"] for c in plot_configs if c["id"] in plot_ui_objects]
|
418 |
-
|
419 |
|
420 |
ui_obj["bomb_button"].click(
|
421 |
-
fn=lambda p_id=plot_id: handle_panel_action(p_id, "insights",
|
422 |
-
inputs=
|
423 |
-
outputs=
|
424 |
api_name=f"action_insights_{plot_id}"
|
425 |
)
|
426 |
ui_obj["formula_button"].click(
|
427 |
-
fn=lambda p_id=plot_id: handle_panel_action(p_id, "formula",
|
428 |
-
inputs=
|
429 |
-
outputs=
|
430 |
api_name=f"action_formula_{plot_id}"
|
431 |
)
|
432 |
ui_obj["explore_button"].click(
|
433 |
-
fn=lambda p_id=plot_id: handle_explore_click(p_id,
|
434 |
-
inputs=
|
435 |
-
outputs=
|
436 |
api_name=f"action_explore_{plot_id}"
|
437 |
)
|
|
|
|
|
|
|
438 |
|
439 |
# --- Function to Refresh All Analytics UI ---
|
440 |
def refresh_all_analytics_ui_elements(current_token_state, date_filter_val, custom_start_val, custom_end_val):
|
@@ -442,88 +436,90 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
442 |
plot_generation_results = update_analytics_plots_figures(
|
443 |
current_token_state, date_filter_val, custom_start_val, custom_end_val
|
444 |
)
|
445 |
-
|
446 |
-
status_message_update = plot_generation_results[0]
|
447 |
-
generated_plot_figures = plot_generation_results[1:]
|
448 |
|
449 |
-
|
|
|
450 |
|
451 |
-
|
452 |
-
p_id_key = config["id"]
|
453 |
-
if p_id_key in plot_ui_objects:
|
454 |
-
if i < len(generated_plot_figures):
|
455 |
-
all_updates.append(generated_plot_figures[i])
|
456 |
-
else:
|
457 |
-
all_updates.append(create_placeholder_plot("Figure Error", f"No figure for {p_id_key}"))
|
458 |
-
else:
|
459 |
-
all_updates.append(None)
|
460 |
|
461 |
-
|
462 |
-
|
463 |
-
|
|
|
|
|
|
|
464 |
|
|
|
|
|
|
|
|
|
|
|
|
|
465 |
for cfg in plot_configs:
|
466 |
pid = cfg["id"]
|
467 |
if pid in plot_ui_objects:
|
468 |
-
all_updates.append(gr.update(value=BOMB_ICON))
|
469 |
-
all_updates.append(gr.update(value=FORMULA_ICON))
|
470 |
-
all_updates.append(gr.update(value=EXPLORE_ICON))
|
471 |
-
all_updates.append(gr.update(visible=True))
|
472 |
-
else:
|
473 |
-
all_updates.extend([None,None,None,None])
|
474 |
-
|
475 |
-
|
476 |
-
all_updates.append(None)
|
477 |
|
|
|
|
|
478 |
logging.info(f"Prepared {len(all_updates)} updates for analytics refresh.")
|
479 |
return all_updates
|
480 |
|
481 |
# --- Define outputs for the apply_filter_btn and sync.then() ---
|
482 |
-
|
483 |
-
|
484 |
-
pid = config["id"]
|
485 |
-
if pid in plot_ui_objects and "plot_component" in plot_ui_objects[pid]:
|
486 |
-
apply_filter_and_sync_outputs.append(plot_ui_objects[pid]["plot_component"])
|
487 |
-
else:
|
488 |
-
apply_filter_and_sync_outputs.append(None) # Must add placeholder if component might be missing
|
489 |
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
494 |
])
|
495 |
|
496 |
-
for
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
apply_filter_and_sync_outputs.append(explored_plot_id_state)
|
510 |
|
511 |
-
logging.info(f"Total outputs for apply_filter/sync: {len(
|
|
|
512 |
|
513 |
apply_filter_btn.click(
|
514 |
fn=refresh_all_analytics_ui_elements,
|
515 |
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
|
516 |
-
outputs=
|
517 |
show_progress="full"
|
518 |
)
|
519 |
|
520 |
with gr.TabItem("3️⃣ Mentions", id="tab_mentions"):
|
521 |
refresh_mentions_display_btn = gr.Button("🔄 Refresh Mentions Display", variant="secondary")
|
522 |
mentions_html = gr.HTML("Mentions data...")
|
523 |
-
mentions_sentiment_dist_plot = gr.Plot(label="Mention Sentiment Distribution")
|
524 |
refresh_mentions_display_btn.click(
|
525 |
fn=run_mentions_tab_display, inputs=[token_state],
|
526 |
-
outputs=[mentions_html, mentions_sentiment_dist_plot],
|
527 |
show_progress="full"
|
528 |
)
|
529 |
|
@@ -541,24 +537,24 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
541 |
outputs=[follower_stats_html, fs_plot_monthly_gains, fs_plot_seniority, fs_plot_industry],
|
542 |
show_progress="full"
|
543 |
)
|
544 |
-
|
545 |
sync_event_part1 = sync_data_btn.click(
|
546 |
fn=sync_all_linkedin_data_orchestrator,
|
547 |
inputs=[token_state], outputs=[sync_status_html_output, token_state], show_progress="full"
|
548 |
)
|
549 |
sync_event_part2 = sync_event_part1.then(
|
550 |
-
fn=process_and_store_bubble_token,
|
551 |
-
inputs=[url_user_token_display, org_urn_display, token_state],
|
552 |
-
outputs=[status_box, token_state, sync_data_btn], show_progress=False
|
553 |
)
|
554 |
sync_event_part3 = sync_event_part2.then(
|
555 |
-
fn=display_main_dashboard,
|
556 |
inputs=[token_state], outputs=[dashboard_display_html], show_progress=False
|
557 |
)
|
558 |
sync_event_final = sync_event_part3.then(
|
559 |
-
fn=refresh_all_analytics_ui_elements,
|
560 |
-
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
|
561 |
-
outputs=
|
562 |
)
|
563 |
|
564 |
if __name__ == "__main__":
|
@@ -575,4 +571,3 @@ if __name__ == "__main__":
|
|
575 |
logging.error("Matplotlib is not installed. Plots will not be generated.")
|
576 |
|
577 |
app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
578 |
-
|
|
|
5 |
import matplotlib
|
6 |
matplotlib.use('Agg') # Set backend for Matplotlib to avoid GUI conflicts with Gradio
|
7 |
import matplotlib.pyplot as plt
|
8 |
+
import time # For profiling if needed
|
9 |
|
10 |
# --- Module Imports ---
|
11 |
from gradio_utils import get_url_user_token
|
|
|
35 |
generate_engagement_rate_over_time_plot,
|
36 |
generate_reach_over_time_plot,
|
37 |
generate_impressions_over_time_plot,
|
38 |
+
create_placeholder_plot,
|
39 |
generate_likes_over_time_plot,
|
40 |
generate_clicks_over_time_plot,
|
41 |
generate_shares_over_time_plot,
|
|
|
52 |
# --- Analytics Tab: Plot Figure Generation Function ---
|
53 |
def update_analytics_plots_figures(token_state_value, date_filter_option, custom_start_date, custom_end_date):
|
54 |
logging.info(f"Updating analytics plot figures. Filter: {date_filter_option}, Custom Start: {custom_start_date}, Custom End: {custom_end_date}")
|
55 |
+
num_expected_plots = 23 # This should match len(plot_configs)
|
56 |
|
57 |
if not token_state_value or not token_state_value.get("token"):
|
58 |
message = "❌ Access denied. No token. Cannot generate analytics."
|
|
|
61 |
return [message] + placeholder_figs
|
62 |
|
63 |
try:
|
64 |
+
# Optional: Start profiling data preparation
|
65 |
+
# data_prep_start_time = time.time()
|
66 |
(filtered_merged_posts_df,
|
67 |
filtered_mentions_df,
|
68 |
date_filtered_follower_stats_df,
|
|
|
71 |
prepare_filtered_analytics_data(
|
72 |
token_state_value, date_filter_option, custom_start_date, custom_end_date
|
73 |
)
|
74 |
+
# logging.info(f"Data preparation took {time.time() - data_prep_start_time:.2f}s")
|
75 |
except Exception as e:
|
76 |
error_msg = f"❌ Error preparing analytics data: {e}"
|
77 |
logging.error(error_msg, exc_info=True)
|
|
|
84 |
eb_labels_col_name = token_state_value.get("config_eb_labels_col", "eb_labels")
|
85 |
|
86 |
plot_figs = []
|
87 |
+
# Optional: For detailed profiling of each plot
|
88 |
+
# individual_plot_times = {}
|
89 |
+
# total_plot_gen_start_time = time.time()
|
90 |
try:
|
91 |
+
# Example for profiling one plot:
|
92 |
+
# plot_name = "posts_activity"
|
93 |
+
# plot_start_time = time.time()
|
94 |
plot_figs.append(generate_posts_activity_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
95 |
+
# individual_plot_times[plot_name] = time.time() - plot_start_time
|
96 |
+
# logging.info(f"Generated {plot_name} in {individual_plot_times[plot_name]:.2f}s")
|
97 |
+
|
98 |
plot_figs.append(generate_engagement_type_plot(filtered_merged_posts_df))
|
99 |
fig_mentions_activity_shared = generate_mentions_activity_plot(filtered_mentions_df, date_column=date_column_mentions)
|
100 |
fig_mention_sentiment_shared = generate_mention_sentiment_plot(filtered_mentions_df)
|
|
|
107 |
plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_industry', plot_title="Followers by Industry"))
|
108 |
plot_figs.append(generate_followers_by_demographics_plot(raw_follower_stats_df, type_value='follower_seniority', plot_title="Followers by Seniority"))
|
109 |
plot_figs.append(generate_engagement_rate_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
110 |
+
plot_figs.append(generate_reach_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts)) # Assuming this is intended, though label says "Clicks"
|
111 |
plot_figs.append(generate_impressions_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
112 |
plot_figs.append(generate_likes_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
113 |
plot_figs.append(generate_clicks_over_time_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
|
|
117 |
plot_figs.append(generate_post_frequency_plot(filtered_merged_posts_df, date_column=date_column_posts))
|
118 |
plot_figs.append(generate_content_format_breakdown_plot(filtered_merged_posts_df, format_col=media_type_col_name))
|
119 |
plot_figs.append(generate_content_topic_breakdown_plot(filtered_merged_posts_df, topics_col=eb_labels_col_name))
|
120 |
+
plot_figs.append(fig_mentions_activity_shared) # Re-adding shared plot for "Mention Analysis (Detailed)"
|
121 |
+
plot_figs.append(fig_mention_sentiment_shared) # Re-adding shared plot for "Mention Analysis (Detailed)"
|
122 |
+
|
123 |
+
# logging.info(f"All plots generated in {time.time() - total_plot_gen_start_time:.2f}s")
|
124 |
+
# logging.info(f"Individual plot generation times: {individual_plot_times}")
|
125 |
|
126 |
message = f"📊 Analytics updated for period: {date_filter_option}"
|
127 |
if date_filter_option == "Custom Range":
|
128 |
s_display = start_dt_for_msg.strftime('%Y-%m-%d') if start_dt_for_msg else "Any"
|
129 |
e_display = end_dt_for_msg.strftime('%Y-%m-%d') if end_dt_for_msg else "Any"
|
130 |
message += f" (From: {s_display} To: {e_display})"
|
131 |
+
|
132 |
final_plot_figs = []
|
133 |
for i, p_fig in enumerate(plot_figs):
|
134 |
+
if p_fig is not None and not isinstance(p_fig, str): # Check if it's a Matplotlib figure
|
135 |
final_plot_figs.append(p_fig)
|
136 |
else:
|
137 |
logging.warning(f"Plot figure generation failed or returned unexpected type for slot {i}, using placeholder. Figure: {p_fig}")
|
138 |
final_plot_figs.append(create_placeholder_plot(title="Plot Error", message="Failed to generate this plot figure."))
|
139 |
+
|
140 |
while len(final_plot_figs) < num_expected_plots:
|
141 |
logging.warning(f"Padding missing plot figure. Expected {num_expected_plots}, got {len(final_plot_figs)}.")
|
142 |
final_plot_figs.append(create_placeholder_plot(title="Missing Plot", message="Plot figure could not be generated."))
|
143 |
+
|
144 |
return [message] + final_plot_figs[:num_expected_plots]
|
145 |
|
146 |
except Exception as e:
|
|
|
189 |
outputs=[status_box, token_state, sync_data_btn, dashboard_display_html],
|
190 |
show_progress="full"
|
191 |
)
|
192 |
+
|
193 |
with gr.TabItem("2️⃣ Analytics", id="tab_analytics"):
|
194 |
gr.Markdown("## 📈 LinkedIn Performance Analytics")
|
195 |
gr.Markdown("Select a date range. Click buttons for actions.")
|
196 |
+
|
197 |
analytics_status_md = gr.Markdown("Analytics status...")
|
198 |
|
199 |
+
with gr.Row():
|
200 |
date_filter_selector = gr.Radio(
|
201 |
["All Time", "Last 7 Days", "Last 30 Days", "Custom Range"],
|
202 |
label="Select Date Range", value="Last 30 Days", scale=3
|
203 |
)
|
204 |
with gr.Column(scale=2):
|
205 |
+
custom_start_date_picker = gr.DateTime(label="Start Date", visible=False, include_time=False, type="datetime") # type="date" might be better if time not used
|
206 |
+
custom_end_date_picker = gr.DateTime(label="End Date", visible=False, include_time=False, type="datetime") # type="date"
|
207 |
+
|
208 |
apply_filter_btn = gr.Button("🔍 Apply Filter & Refresh Analytics", variant="primary")
|
209 |
|
210 |
def toggle_custom_date_pickers(selection):
|
|
|
220 |
plot_configs = [
|
221 |
{"label": "Posts Activity Over Time", "id": "posts_activity", "section": "Posts & Engagement Overview"},
|
222 |
{"label": "Post Engagement Types", "id": "engagement_type", "section": "Posts & Engagement Overview"},
|
223 |
+
{"label": "Mentions Activity Over Time", "id": "mentions_activity", "section": "Mentions Overview"},
|
224 |
+
{"label": "Mention Sentiment Distribution", "id": "mention_sentiment", "section": "Mentions Overview"},
|
225 |
{"label": "Followers Count Over Time", "id": "followers_count", "section": "Follower Dynamics"},
|
226 |
{"label": "Followers Growth Rate", "id": "followers_growth_rate", "section": "Follower Dynamics"},
|
227 |
{"label": "Followers by Location", "id": "followers_by_location", "section": "Follower Demographics"},
|
|
|
229 |
{"label": "Followers by Industry", "id": "followers_by_industry", "section": "Follower Demographics"},
|
230 |
{"label": "Followers by Seniority", "id": "followers_by_seniority", "section": "Follower Demographics"},
|
231 |
{"label": "Engagement Rate Over Time", "id": "engagement_rate", "section": "Post Performance Insights"},
|
232 |
+
{"label": "Reach Over Time", "id": "reach_over_time", "section": "Post Performance Insights"}, # Corrected label based on function
|
233 |
{"label": "Impressions Over Time", "id": "impressions_over_time", "section": "Post Performance Insights"},
|
234 |
{"label": "Reactions (Likes) Over Time", "id": "likes_over_time", "section": "Post Performance Insights"},
|
235 |
{"label": "Clicks Over Time", "id": "clicks_over_time", "section": "Detailed Post Engagement Over Time"},
|
|
|
239 |
{"label": "Post Frequency", "id": "post_frequency_cs", "section": "Content Strategy Analysis"},
|
240 |
{"label": "Breakdown of Content by Format", "id": "content_format_breakdown_cs", "section": "Content Strategy Analysis"},
|
241 |
{"label": "Breakdown of Content by Topics", "id": "content_topic_breakdown_cs", "section": "Content Strategy Analysis"},
|
242 |
+
{"label": "Mentions Volume Over Time (Detailed)", "id": "mention_analysis_volume", "section": "Mention Analysis (Detailed)"},
|
243 |
+
{"label": "Breakdown of Mentions by Sentiment (Detailed)", "id": "mention_analysis_sentiment", "section": "Mention Analysis (Detailed)"}
|
244 |
]
|
245 |
assert len(plot_configs) == 23, "Mismatch in plot_configs and expected plots."
|
246 |
|
247 |
+
active_panel_action_state = gr.State(None) # Stores {"plot_id": "action_type"} e.g. {"posts_activity": "insights"} or None
|
248 |
+
explored_plot_id_state = gr.State(None) # Stores plot_id of the currently explored plot, or None
|
249 |
+
|
250 |
+
plot_ui_objects = {} # Will be populated by build_analytics_tab_plot_area
|
|
|
251 |
|
252 |
+
with gr.Row(equal_height=False):
|
253 |
+
with gr.Column(scale=8) as plots_area_col:
|
254 |
+
# Call the UI builder function and store its results
|
255 |
plot_ui_objects = build_analytics_tab_plot_area(plot_configs)
|
256 |
+
|
257 |
+
with gr.Column(scale=4, visible=False) as global_actions_column_ui:
|
258 |
+
gr.Markdown("### 💡 Generated Content")
|
259 |
global_actions_markdown_ui = gr.Markdown("Click a button (💣, ƒ) on a plot to see content here.")
|
260 |
|
261 |
|
262 |
# --- Event Handler for Insights and Formula Buttons ---
|
263 |
+
def handle_panel_action(plot_id_clicked, action_type, current_active_action_from_state, current_token_state_val):
|
264 |
+
logging.info(f"Action '{action_type}' for plot: {plot_id_clicked}. Current active from state: {current_active_action_from_state}")
|
265 |
+
|
266 |
+
# This check is crucial: plot_ui_objects must be populated before this handler is effectively used.
|
267 |
+
# It should be, as this handler is tied to buttons created by build_analytics_tab_plot_area.
|
268 |
+
if not plot_ui_objects or plot_id_clicked not in plot_ui_objects:
|
269 |
+
logging.error(f"plot_ui_objects not populated or plot_id {plot_id_clicked} not found during handle_panel_action.")
|
270 |
+
# Return updates that don't crash, perhaps just an error message or no-ops.
|
271 |
+
# Number of outputs for this handler: 3 (global_col_vis, global_md_val, active_state_val) + 2 * len(plot_configs) (buttons)
|
272 |
+
error_updates = [gr.update(visible=False), "Error: UI components not ready.", None] + [gr.update() for _ in range(2 * len(plot_configs))]
|
273 |
+
return error_updates
|
274 |
+
|
275 |
+
|
276 |
clicked_plot_label = plot_ui_objects.get(plot_id_clicked, {}).get("label", "Selected Plot")
|
277 |
|
278 |
+
hypothetical_new_active_state = {"plot_id": plot_id_clicked, "type": action_type}
|
279 |
+
is_toggling_off = current_active_action_from_state == hypothetical_new_active_state
|
|
|
280 |
|
281 |
+
new_active_action_state_to_set = None
|
282 |
content_text = ""
|
283 |
action_col_visible = False
|
|
|
|
|
|
|
284 |
|
285 |
if is_toggling_off:
|
286 |
+
new_active_action_state_to_set = None # Closing the panel
|
287 |
+
content_text = f"{action_type.capitalize()} panel for '{clicked_plot_label}' closed."
|
288 |
action_col_visible = False
|
289 |
logging.info(f"Closing {action_type} panel for {plot_id_clicked}")
|
|
|
|
|
|
|
|
|
|
|
290 |
else: # Activating or switching
|
291 |
+
new_active_action_state_to_set = hypothetical_new_active_state
|
292 |
action_col_visible = True
|
293 |
if action_type == "insights":
|
294 |
+
# TODO: Implement actual insight generation using current_token_state_val if needed
|
295 |
+
content_text = f"**Insights for: {clicked_plot_label}**\n\nPlot ID: `{plot_id_clicked}`.\n(AI insights generation placeholder)"
|
|
|
296 |
elif action_type == "formula":
|
297 |
# TODO: Implement actual formula display
|
298 |
+
content_text = f"**Formula/Methodology for: {clicked_plot_label}**\n\nPlot ID: `{plot_id_clicked}`.\n(Methodology details placeholder)"
|
299 |
+
logging.info(f"Opening/switching to {action_type} panel for {plot_id_clicked}")
|
300 |
+
|
301 |
+
# Prepare updates for ALL buttons based on new_active_action_state_to_set
|
302 |
+
all_button_updates = []
|
303 |
+
for cfg_item in plot_configs:
|
304 |
+
p_id_iter = cfg_item["id"]
|
305 |
+
if p_id_iter in plot_ui_objects:
|
306 |
+
# Bomb button state for this p_id_iter
|
307 |
+
if new_active_action_state_to_set == {"plot_id": p_id_iter, "type": "insights"}:
|
308 |
+
all_button_updates.append(gr.update(value=ACTIVE_ICON))
|
309 |
+
else:
|
310 |
+
all_button_updates.append(gr.update(value=BOMB_ICON))
|
311 |
+
|
312 |
+
# Formula button state for this p_id_iter
|
313 |
+
if new_active_action_state_to_set == {"plot_id": p_id_iter, "type": "formula"}:
|
314 |
+
all_button_updates.append(gr.update(value=ACTIVE_ICON))
|
315 |
+
else:
|
316 |
+
all_button_updates.append(gr.update(value=FORMULA_ICON))
|
317 |
+
else: # Should not happen if plot_ui_objects is complete
|
318 |
+
all_button_updates.extend([gr.update(), gr.update()])
|
319 |
|
|
|
|
|
320 |
final_updates = [
|
321 |
+
gr.update(visible=action_col_visible),
|
322 |
+
gr.update(value=content_text),
|
323 |
+
new_active_action_state_to_set # This is the new value for active_panel_action_state
|
324 |
+
] + all_button_updates
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
|
326 |
return final_updates
|
327 |
|
328 |
# --- Event Handler for Explore Button ---
|
329 |
+
def handle_explore_click(plot_id_clicked, current_explored_plot_id_from_state):
|
330 |
+
logging.info(f"Explore clicked for: {plot_id_clicked}. Currently explored from state: {current_explored_plot_id_from_state}")
|
331 |
+
|
332 |
+
if not plot_ui_objects: # Safeguard
|
333 |
+
logging.error("plot_ui_objects not populated during handle_explore_click.")
|
334 |
+
return [current_explored_plot_id_from_state] + [gr.update() for _ in range(2 * len(plot_configs))]
|
335 |
|
336 |
+
|
337 |
+
new_explored_id_to_set = None
|
338 |
+
is_toggling_off = (plot_id_clicked == current_explored_plot_id_from_state)
|
339 |
|
340 |
if is_toggling_off:
|
341 |
+
new_explored_id_to_set = None # Un-exploring
|
|
|
342 |
logging.info(f"Un-exploring plot: {plot_id_clicked}")
|
343 |
else:
|
344 |
+
new_explored_id_to_set = plot_id_clicked # Exploring this plot
|
|
|
|
|
|
|
345 |
logging.info(f"Exploring plot: {plot_id_clicked}")
|
346 |
|
347 |
+
# Prepare updates for panel visibility and explore button icons
|
348 |
+
panel_and_button_updates = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
349 |
for cfg in plot_configs:
|
350 |
p_id = cfg["id"]
|
351 |
if p_id in plot_ui_objects:
|
352 |
+
panel_visible = not new_explored_id_to_set or (p_id == new_explored_id_to_set)
|
353 |
+
|
354 |
+
# Special handling for paired plots in the same section when one is explored
|
355 |
+
# This logic might need refinement based on how build_analytics_tab_plot_area structures rows
|
356 |
+
# For now, the basic logic is: if new_explored_id_to_set is active, only that panel is visible.
|
357 |
+
# If new_explored_id_to_set is None, all panels are visible.
|
358 |
+
|
359 |
+
panel_and_button_updates.append(gr.update(visible=panel_visible)) # Panel visibility update
|
360 |
|
361 |
+
# Explore button icon update
|
362 |
+
if p_id == new_explored_id_to_set: # If this plot is the one being explored (or just became explored)
|
363 |
+
panel_and_button_updates.append(gr.update(value=ACTIVE_ICON))
|
364 |
+
else: # All other plots, or if un-exploring
|
365 |
+
panel_and_button_updates.append(gr.update(value=EXPLORE_ICON))
|
366 |
else: # Should not happen
|
367 |
+
panel_and_button_updates.extend([gr.update(), gr.update()])
|
368 |
|
369 |
+
final_updates = [new_explored_id_to_set] + panel_and_button_updates
|
370 |
return final_updates
|
371 |
|
372 |
+
# --- Define outputs for event handlers ---
|
373 |
+
# These lists define which Gradio components will receive updates from the handlers.
|
374 |
+
# The order and number of components must match the list of updates returned by the handler.
|
375 |
+
|
376 |
+
# Outputs for handle_panel_action (Insights/Formula buttons)
|
377 |
+
action_buttons_outputs_list = [
|
378 |
+
global_actions_column_ui,
|
379 |
+
global_actions_markdown_ui,
|
380 |
+
active_panel_action_state # The gr.State object itself
|
381 |
]
|
382 |
+
for cfg_item_action in plot_configs: # Iterate in defined order
|
383 |
+
pid_action = cfg_item_action["id"]
|
384 |
+
if pid_action in plot_ui_objects:
|
385 |
+
action_buttons_outputs_list.append(plot_ui_objects[pid_action]["bomb_button"])
|
386 |
+
action_buttons_outputs_list.append(plot_ui_objects[pid_action]["formula_button"])
|
387 |
+
else: # Should not happen if plot_ui_objects is correctly populated
|
388 |
+
action_buttons_outputs_list.extend([None, None]) # Add placeholders to maintain length
|
389 |
+
|
390 |
+
# Outputs for handle_explore_click
|
391 |
+
explore_buttons_outputs_list = [explored_plot_id_state] # The gr.State object
|
392 |
+
for cfg_item_explore in plot_configs: # Iterate in defined order
|
393 |
+
pid_explore = cfg_item_explore["id"]
|
394 |
+
if pid_explore in plot_ui_objects:
|
395 |
+
explore_buttons_outputs_list.append(plot_ui_objects[pid_explore]["panel_component"]) # For visibility
|
396 |
+
explore_buttons_outputs_list.append(plot_ui_objects[pid_explore]["explore_button"]) # For icon
|
397 |
+
else: # Should not happen
|
398 |
+
explore_buttons_outputs_list.extend([None, None])
|
399 |
|
400 |
|
401 |
+
# --- Connect Action Buttons ---
|
402 |
+
# Inputs for the click handlers that need current state values
|
403 |
+
action_click_inputs = [active_panel_action_state, token_state]
|
404 |
+
explore_click_inputs = [explored_plot_id_state] # Only needs explored_plot_id_state
|
405 |
+
|
406 |
for config_item in plot_configs:
|
407 |
plot_id = config_item["id"]
|
408 |
+
if plot_id in plot_ui_objects: # Ensure component exists
|
409 |
ui_obj = plot_ui_objects[plot_id]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
410 |
|
411 |
ui_obj["bomb_button"].click(
|
412 |
+
fn=lambda current_active_val, current_token_val, p_id=plot_id: handle_panel_action(p_id, "insights", current_active_val, current_token_val),
|
413 |
+
inputs=action_click_inputs,
|
414 |
+
outputs=action_buttons_outputs_list,
|
415 |
api_name=f"action_insights_{plot_id}"
|
416 |
)
|
417 |
ui_obj["formula_button"].click(
|
418 |
+
fn=lambda current_active_val, current_token_val, p_id=plot_id: handle_panel_action(p_id, "formula", current_active_val, current_token_val),
|
419 |
+
inputs=action_click_inputs,
|
420 |
+
outputs=action_buttons_outputs_list,
|
421 |
api_name=f"action_formula_{plot_id}"
|
422 |
)
|
423 |
ui_obj["explore_button"].click(
|
424 |
+
fn=lambda current_explored_val, p_id=plot_id: handle_explore_click(p_id, current_explored_val),
|
425 |
+
inputs=explore_click_inputs,
|
426 |
+
outputs=explore_buttons_outputs_list,
|
427 |
api_name=f"action_explore_{plot_id}"
|
428 |
)
|
429 |
+
else:
|
430 |
+
logging.warning(f"UI object for plot_id '{plot_id}' not found when trying to attach click handlers. This might lead to issues.")
|
431 |
+
|
432 |
|
433 |
# --- Function to Refresh All Analytics UI ---
|
434 |
def refresh_all_analytics_ui_elements(current_token_state, date_filter_val, custom_start_val, custom_end_val):
|
|
|
436 |
plot_generation_results = update_analytics_plots_figures(
|
437 |
current_token_state, date_filter_val, custom_start_val, custom_end_val
|
438 |
)
|
|
|
|
|
|
|
439 |
|
440 |
+
status_message_update = plot_generation_results[0]
|
441 |
+
generated_plot_figures = plot_generation_results[1:] # Should be list of 23 figures or placeholders
|
442 |
|
443 |
+
all_updates = [status_message_update] # For analytics_status_md
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
444 |
|
445 |
+
# Updates for plot components
|
446 |
+
for i in range(len(plot_configs)):
|
447 |
+
if i < len(generated_plot_figures):
|
448 |
+
all_updates.append(generated_plot_figures[i])
|
449 |
+
else: # Should not happen if update_analytics_plots_figures pads correctly
|
450 |
+
all_updates.append(create_placeholder_plot("Figure Error", f"Missing figure for plot {i}"))
|
451 |
|
452 |
+
# Updates for global action column and its content, and active_panel_action_state
|
453 |
+
all_updates.append(gr.update(visible=False)) # global_actions_column_ui
|
454 |
+
all_updates.append(gr.update(value="Click a button (💣, ƒ) on a plot...")) # global_actions_markdown_ui
|
455 |
+
all_updates.append(None) # Reset active_panel_action_state
|
456 |
+
|
457 |
+
# Updates for all action buttons (bomb, formula), explore buttons, and panel visibility
|
458 |
for cfg in plot_configs:
|
459 |
pid = cfg["id"]
|
460 |
if pid in plot_ui_objects:
|
461 |
+
all_updates.append(gr.update(value=BOMB_ICON)) # Reset bomb_button
|
462 |
+
all_updates.append(gr.update(value=FORMULA_ICON)) # Reset formula_button
|
463 |
+
all_updates.append(gr.update(value=EXPLORE_ICON)) # Reset explore_button
|
464 |
+
all_updates.append(gr.update(visible=True)) # Reset panel_component visibility (all visible initially)
|
465 |
+
else: # Should not happen
|
466 |
+
all_updates.extend([None, None, None, None])
|
|
|
|
|
|
|
467 |
|
468 |
+
all_updates.append(None) # Reset explored_plot_id_state
|
469 |
+
|
470 |
logging.info(f"Prepared {len(all_updates)} updates for analytics refresh.")
|
471 |
return all_updates
|
472 |
|
473 |
# --- Define outputs for the apply_filter_btn and sync.then() ---
|
474 |
+
# This list must exactly match the structure of updates returned by refresh_all_analytics_ui_elements
|
475 |
+
apply_filter_and_sync_outputs_list = [analytics_status_md] # 1. Status MD
|
|
|
|
|
|
|
|
|
|
|
476 |
|
477 |
+
# 2. Plot components (23 of them)
|
478 |
+
for config_item_filter_sync in plot_configs:
|
479 |
+
pid_filter_sync = config_item_filter_sync["id"]
|
480 |
+
if pid_filter_sync in plot_ui_objects and "plot_component" in plot_ui_objects[pid_filter_sync]:
|
481 |
+
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync]["plot_component"])
|
482 |
+
else: # Should not happen
|
483 |
+
apply_filter_and_sync_outputs_list.append(None)
|
484 |
+
|
485 |
+
# 3. Global action column, its markdown, and its state (3 items)
|
486 |
+
apply_filter_and_sync_outputs_list.extend([
|
487 |
+
global_actions_column_ui,
|
488 |
+
global_actions_markdown_ui,
|
489 |
+
active_panel_action_state # The State object itself
|
490 |
])
|
491 |
|
492 |
+
# 4. Action buttons (bomb, formula), explore buttons, and panel visibility for each plot (23 * 4 = 92 items)
|
493 |
+
for cfg_filter_sync_btns in plot_configs:
|
494 |
+
pid_filter_sync_btns = cfg_filter_sync_btns["id"]
|
495 |
+
if pid_filter_sync_btns in plot_ui_objects:
|
496 |
+
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["bomb_button"])
|
497 |
+
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["formula_button"])
|
498 |
+
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["explore_button"])
|
499 |
+
apply_filter_and_sync_outputs_list.append(plot_ui_objects[pid_filter_sync_btns]["panel_component"]) # For panel visibility
|
500 |
+
else: # Should not happen
|
501 |
+
apply_filter_and_sync_outputs_list.extend([None, None, None, None])
|
502 |
+
|
503 |
+
# 5. Explored plot ID state (1 item)
|
504 |
+
apply_filter_and_sync_outputs_list.append(explored_plot_id_state) # The State object itself
|
|
|
505 |
|
506 |
+
logging.info(f"Total outputs for apply_filter/sync: {len(apply_filter_and_sync_outputs_list)}")
|
507 |
+
|
508 |
|
509 |
apply_filter_btn.click(
|
510 |
fn=refresh_all_analytics_ui_elements,
|
511 |
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
|
512 |
+
outputs=apply_filter_and_sync_outputs_list, # Use the carefully constructed list
|
513 |
show_progress="full"
|
514 |
)
|
515 |
|
516 |
with gr.TabItem("3️⃣ Mentions", id="tab_mentions"):
|
517 |
refresh_mentions_display_btn = gr.Button("🔄 Refresh Mentions Display", variant="secondary")
|
518 |
mentions_html = gr.HTML("Mentions data...")
|
519 |
+
mentions_sentiment_dist_plot = gr.Plot(label="Mention Sentiment Distribution") # This is a gr.Plot component
|
520 |
refresh_mentions_display_btn.click(
|
521 |
fn=run_mentions_tab_display, inputs=[token_state],
|
522 |
+
outputs=[mentions_html, mentions_sentiment_dist_plot], # run_mentions_tab_display returns (html_str, fig_object)
|
523 |
show_progress="full"
|
524 |
)
|
525 |
|
|
|
537 |
outputs=[follower_stats_html, fs_plot_monthly_gains, fs_plot_seniority, fs_plot_industry],
|
538 |
show_progress="full"
|
539 |
)
|
540 |
+
|
541 |
sync_event_part1 = sync_data_btn.click(
|
542 |
fn=sync_all_linkedin_data_orchestrator,
|
543 |
inputs=[token_state], outputs=[sync_status_html_output, token_state], show_progress="full"
|
544 |
)
|
545 |
sync_event_part2 = sync_event_part1.then(
|
546 |
+
fn=process_and_store_bubble_token, # This fn returns (status_msg, new_state, btn_update)
|
547 |
+
inputs=[url_user_token_display, org_urn_display, token_state],
|
548 |
+
outputs=[status_box, token_state, sync_data_btn], show_progress=False # btn_update is for sync_data_btn
|
549 |
)
|
550 |
sync_event_part3 = sync_event_part2.then(
|
551 |
+
fn=display_main_dashboard, # This fn returns html_string
|
552 |
inputs=[token_state], outputs=[dashboard_display_html], show_progress=False
|
553 |
)
|
554 |
sync_event_final = sync_event_part3.then(
|
555 |
+
fn=refresh_all_analytics_ui_elements, # This fn returns a list of updates
|
556 |
+
inputs=[token_state, date_filter_selector, custom_start_date_picker, custom_end_date_picker],
|
557 |
+
outputs=apply_filter_and_sync_outputs_list, show_progress="full" # Use the carefully constructed list
|
558 |
)
|
559 |
|
560 |
if __name__ == "__main__":
|
|
|
571 |
logging.error("Matplotlib is not installed. Plots will not be generated.")
|
572 |
|
573 |
app.launch(server_name="0.0.0.0", server_port=7860, debug=True)
|
|