Spaces:
Running
Running
# app.py | |
# -- coding: utf-8 -- | |
import gradio as gr | |
import pandas as pd | |
import os | |
import logging | |
import time # Added for simulating delay | |
# --- Module Imports --- | |
# Functions from your existing/provided custom modules | |
from analytics_fetch_and_rendering import fetch_and_render_analytics # Assuming this exists | |
from gradio_utils import get_url_user_token # For fetching URL parameters | |
# Functions from newly created/refactored modules | |
from config import ( | |
LINKEDIN_CLIENT_ID_ENV_VAR, BUBBLE_APP_NAME_ENV_VAR, | |
BUBBLE_API_KEY_PRIVATE_ENV_VAR, BUBBLE_API_ENDPOINT_ENV_VAR | |
) | |
from state_manager import process_and_store_bubble_token | |
from sync_logic import sync_all_linkedin_data_orchestrator | |
from ui_generators import ( | |
display_main_dashboard, | |
run_mentions_tab_display, | |
run_follower_stats_tab_display | |
) | |
# Configure logging | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') | |
# --- Helper function to load HTML animation --- | |
def get_sync_animation_html(): | |
"""Loads the HTML content for the sync animation.""" | |
try: | |
# Ensure this path is correct relative to where app.py is run | |
with open("sync_animation.html", "r", encoding="utf-8") as f: | |
return f.read() | |
except FileNotFoundError: | |
logging.error("sync_animation.html not found. Please ensure it's in the same directory as app.py.") | |
return "<p style='text-align:center; color: red;'>Animation file not found. Syncing...</p>" | |
except Exception as e: | |
logging.error(f"Error loading sync_animation.html: {e}") | |
return f"<p style='text-align:center; color: red;'>Error loading animation: {e}. Syncing...</p>" | |
# --- Guarded Analytics Fetch --- | |
def guarded_fetch_analytics(token_state): | |
"""Guarded call to fetch_and_render_analytics, ensuring token and basic data structures.""" | |
if not token_state or not token_state.get("token"): | |
logging.warning("Analytics fetch: Access denied. No token.") | |
return ("β Access denied. No token.", None, None, None, None, None, None, None) | |
posts_df_analytics = token_state.get("bubble_posts_df", pd.DataFrame()) | |
mentions_df_analytics = token_state.get("bubble_mentions_df", pd.DataFrame()) | |
follower_stats_df_analytics = token_state.get("bubble_follower_stats_df", pd.DataFrame()) | |
logging.info("Calling fetch_and_render_analytics with current token_state data.") | |
try: | |
return fetch_and_render_analytics( | |
token_state.get("client_id"), | |
token_state.get("token"), | |
token_state.get("org_urn"), | |
posts_df_analytics, | |
mentions_df_analytics, | |
follower_stats_df_analytics | |
) | |
except Exception as e: | |
logging.error(f"Error in guarded_fetch_analytics calling fetch_and_render_analytics: {e}", exc_info=True) | |
return (f"β Error fetching analytics: {e}", None, None, None, None, None, None, None) | |
# --- Animation Test Functions --- | |
def show_animation_for_test(): | |
"""Returns the animation HTML for the test button.""" | |
logging.info("TEST BUTTON: Showing animation.") | |
return get_sync_animation_html() | |
def simulate_processing_after_animation(): | |
"""Simulates a delay and then returns a completion message.""" | |
logging.info("TEST BUTTON: Simulating processing after animation.") | |
time.sleep(5) # Simulate a 5-second process | |
logging.info("TEST BUTTON: Simulation complete.") | |
return "<p style='text-align:center; color: green; font-size: 1.2em;'>β Animation Test Complete!</p>" | |
# --- Gradio UI Blocks --- | |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"), | |
title="LinkedIn Organization Dashboard") as app: | |
token_state = gr.State(value={ | |
"token": None, "client_id": None, "org_urn": None, | |
"bubble_posts_df": pd.DataFrame(), "fetch_count_for_api": 0, | |
"bubble_mentions_df": pd.DataFrame(), | |
"bubble_follower_stats_df": pd.DataFrame(), | |
"bubble_operations_log_df": pd.DataFrame(), | |
"mentions_should_sync_now": False, | |
"fs_should_sync_now": False, | |
"url_user_token_temp_storage": None | |
}) | |
gr.Markdown("# π LinkedIn Organization Dashboard") | |
url_user_token_display = gr.Textbox(label="User Token (from URL - Hidden)", interactive=False, visible=False) | |
status_box = gr.Textbox(label="Overall LinkedIn Token Status", interactive=False, value="Initializing...") | |
org_urn_display = gr.Textbox(label="Organization URN (from URL - Hidden)", interactive=False, visible=False) | |
app.load(fn=get_url_user_token, inputs=None, outputs=[url_user_token_display, org_urn_display], api_name="get_url_params", show_progress=False) | |
def initial_load_sequence(url_token, org_urn_val, current_state): | |
logging.info(f"Initial load sequence triggered. Org URN: {org_urn_val}, URL Token: {'Present' if url_token else 'Absent'}") | |
status_msg, new_state, btn_update = process_and_store_bubble_token(url_token, org_urn_val, current_state) | |
dashboard_content = display_main_dashboard(new_state) | |
return status_msg, new_state, btn_update, dashboard_content | |
with gr.Tabs(): | |
with gr.TabItem("1οΈβ£ Dashboard & Sync"): | |
gr.Markdown("System checks for existing data from Bubble. The 'Sync' button activates if new data needs to be fetched from LinkedIn based on the last sync times and data availability.") | |
with gr.Row(): # To place buttons side-by-side or in a sequence | |
sync_data_btn = gr.Button("π Sync LinkedIn Data", variant="primary", visible=False, interactive=False) | |
test_animation_btn = gr.Button("π§ͺ Test Animation Display", variant="secondary") # New Test Button | |
sync_status_html_output = gr.HTML("<p style='text-align:center;'>Sync status will appear here.</p>") | |
dashboard_display_html = gr.HTML("<p style='text-align:center;'>Dashboard loading...</p>") | |
org_urn_display.change( | |
fn=initial_load_sequence, | |
inputs=[url_user_token_display, org_urn_display, token_state], | |
outputs=[status_box, token_state, sync_data_btn, dashboard_display_html], | |
show_progress="full" | |
) | |
# Original Sync Button Logic | |
sync_data_btn.click( | |
fn=lambda: get_sync_animation_html(), | |
inputs=None, | |
outputs=[sync_status_html_output], | |
show_progress=False | |
).then( | |
fn=sync_all_linkedin_data_orchestrator, | |
inputs=[token_state], | |
outputs=[sync_status_html_output, token_state], | |
show_progress=False # Animation is the progress | |
).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 | |
).then( | |
fn=display_main_dashboard, | |
inputs=[token_state], | |
outputs=[dashboard_display_html], | |
show_progress=False | |
) | |
# New Test Animation Button Logic | |
test_animation_btn.click( | |
fn=show_animation_for_test, | |
inputs=None, | |
outputs=[sync_status_html_output], | |
show_progress=False # Animation is the progress | |
).then( | |
fn=simulate_processing_after_animation, | |
inputs=None, | |
outputs=[sync_status_html_output], | |
show_progress=False # Message indicates completion | |
) | |
with gr.TabItem("2οΈβ£ Analytics"): | |
fetch_analytics_btn = gr.Button("π Fetch/Refresh Full Analytics", variant="primary") | |
follower_count_md = gr.Markdown("Analytics data will load here...") | |
with gr.Row(): follower_plot, growth_plot = gr.Plot(label="Follower Demographics"), gr.Plot(label="Follower Growth") | |
with gr.Row(): eng_rate_plot = gr.Plot(label="Engagement Rate") | |
with gr.Row(): interaction_plot = gr.Plot(label="Post Interactions") | |
with gr.Row(): eb_plot = gr.Plot(label="Engagement Benchmark") | |
with gr.Row(): mentions_vol_plot, mentions_sentiment_plot = gr.Plot(label="Mentions Volume"), gr.Plot(label="Mentions Sentiment") | |
fetch_analytics_btn.click( | |
fn=guarded_fetch_analytics, inputs=[token_state], | |
outputs=[follower_count_md, follower_plot, growth_plot, eng_rate_plot, | |
interaction_plot, eb_plot, mentions_vol_plot, mentions_sentiment_plot], | |
show_progress="full" | |
) | |
with gr.TabItem("3οΈβ£ Mentions"): | |
refresh_mentions_display_btn = gr.Button("π Refresh Mentions Display (from local data)", variant="secondary") | |
mentions_html = gr.HTML("Mentions data loads from Bubble after sync. Click refresh to view current local data.") | |
mentions_sentiment_dist_plot = gr.Plot(label="Mention Sentiment Distribution") | |
refresh_mentions_display_btn.click( | |
fn=run_mentions_tab_display, inputs=[token_state], | |
outputs=[mentions_html, mentions_sentiment_dist_plot], | |
show_progress="full" | |
) | |
with gr.TabItem("4οΈβ£ Follower Stats"): | |
refresh_follower_stats_btn = gr.Button("π Refresh Follower Stats Display (from local data)", variant="secondary") | |
follower_stats_html = gr.HTML("Follower statistics load from Bubble after sync. Click refresh to view current local data.") | |
with gr.Row(): | |
fs_plot_monthly_gains = gr.Plot(label="Monthly Follower Gains") | |
with gr.Row(): | |
fs_plot_seniority = gr.Plot(label="Followers by Seniority (Top 10 Organic)") | |
fs_plot_industry = gr.Plot(label="Followers by Industry (Top 10 Organic)") | |
refresh_follower_stats_btn.click( | |
fn=run_follower_stats_tab_display, inputs=[token_state], | |
outputs=[follower_stats_html, fs_plot_monthly_gains, fs_plot_seniority, fs_plot_industry], | |
show_progress="full" | |
) | |
if __name__ == "__main__": | |
if not os.environ.get(LINKEDIN_CLIENT_ID_ENV_VAR): | |
logging.warning(f"WARNING: '{LINKEDIN_CLIENT_ID_ENV_VAR}' environment variable not set. The app may not function correctly for LinkedIn API calls.") | |
if not os.environ.get(BUBBLE_APP_NAME_ENV_VAR) or \ | |
not os.environ.get(BUBBLE_API_KEY_PRIVATE_ENV_VAR) or \ | |
not os.environ.get(BUBBLE_API_ENDPOINT_ENV_VAR): | |
logging.warning("WARNING: One or more Bubble environment variables (BUBBLE_APP_NAME, BUBBLE_API_KEY_PRIVATE, BUBBLE_API_ENDPOINT) are not set. Bubble integration will fail.") | |
try: | |
import matplotlib | |
logging.info(f"Matplotlib version: {matplotlib.__version__} found. Backend: {matplotlib.get_backend()}") | |
except ImportError: | |
logging.error("Matplotlib is not installed. Plots will not be generated. Please install it: pip install matplotlib") | |
app.launch(server_name="0.0.0.0", server_port=7860, debug=True, share=False) | |