GuglielmoTor commited on
Commit
6f117a4
Β·
verified Β·
1 Parent(s): bb15901

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -39
app.py CHANGED
@@ -32,6 +32,7 @@ def get_sync_animation_html():
32
  """Loads the HTML content for the sync animation."""
33
  try:
34
  # Ensure this path is correct relative to where app.py is run
 
35
  with open("sync_animation.html", "r", encoding="utf-8") as f:
36
  return f.read()
37
  except FileNotFoundError:
@@ -48,12 +49,14 @@ def guarded_fetch_analytics(token_state):
48
  logging.warning("Analytics fetch: Access denied. No token.")
49
  return ("❌ Access denied. No token.", None, None, None, None, None, None, None)
50
 
 
51
  posts_df_analytics = token_state.get("bubble_posts_df", pd.DataFrame())
52
  mentions_df_analytics = token_state.get("bubble_mentions_df", pd.DataFrame())
53
  follower_stats_df_analytics = token_state.get("bubble_follower_stats_df", pd.DataFrame())
54
 
55
  logging.info("Calling fetch_and_render_analytics with current token_state data.")
56
  try:
 
57
  return fetch_and_render_analytics(
58
  token_state.get("client_id"),
59
  token_state.get("token"),
@@ -66,18 +69,20 @@ def guarded_fetch_analytics(token_state):
66
  logging.error(f"Error in guarded_fetch_analytics calling fetch_and_render_analytics: {e}", exc_info=True)
67
  return (f"❌ Error fetching analytics: {e}", None, None, None, None, None, None, None)
68
 
69
- # --- Animation Test Functions ---
70
- def show_animation_for_test():
71
- """Returns the animation HTML for the test button."""
72
- logging.info("TEST BUTTON: Showing animation.")
73
- return get_sync_animation_html()
 
 
 
74
 
75
- def simulate_processing_after_animation():
76
- """Simulates a delay and then returns a completion message."""
77
- logging.info("TEST BUTTON: Simulating processing after animation.")
78
- time.sleep(5) # Simulate a 5-second process
79
- logging.info("TEST BUTTON: Simulation complete.")
80
- return "<p style='text-align:center; color: green; font-size: 1.2em;'>βœ… Animation Test Complete!</p>"
81
 
82
 
83
  # --- Gradio UI Blocks ---
@@ -92,34 +97,57 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
92
  "bubble_operations_log_df": pd.DataFrame(),
93
  "mentions_should_sync_now": False,
94
  "fs_should_sync_now": False,
95
- "url_user_token_temp_storage": None
96
  })
97
 
98
  gr.Markdown("# πŸš€ LinkedIn Organization Dashboard")
 
99
  url_user_token_display = gr.Textbox(label="User Token (from URL - Hidden)", interactive=False, visible=False)
100
  status_box = gr.Textbox(label="Overall LinkedIn Token Status", interactive=False, value="Initializing...")
101
  org_urn_display = gr.Textbox(label="Organization URN (from URL - Hidden)", interactive=False, visible=False)
102
 
 
 
 
 
103
  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)
104
 
105
- def initial_load_sequence(url_token, org_urn_val, current_state):
106
- logging.info(f"Initial load sequence triggered. Org URN: {org_urn_val}, URL Token: {'Present' if url_token else 'Absent'}")
 
 
 
 
 
 
 
 
 
 
 
 
107
  status_msg, new_state, btn_update = process_and_store_bubble_token(url_token, org_urn_val, current_state)
108
- dashboard_content = display_main_dashboard(new_state)
109
- return status_msg, new_state, btn_update, dashboard_content
 
 
 
110
 
111
  with gr.Tabs():
112
  with gr.TabItem("1️⃣ Dashboard & Sync"):
113
  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.")
114
 
115
- with gr.Row(): # To place buttons side-by-side or in a sequence
116
  sync_data_btn = gr.Button("πŸ”„ Sync LinkedIn Data", variant="primary", visible=False, interactive=False)
117
- test_animation_btn = gr.Button("πŸ§ͺ Test Animation Display", variant="secondary") # New Test Button
118
 
 
119
  sync_status_html_output = gr.HTML("<p style='text-align:center;'>Sync status will appear here.</p>")
120
 
 
121
  dashboard_display_html = gr.HTML("<p style='text-align:center;'>Dashboard loading...</p>")
122
 
 
123
  org_urn_display.change(
124
  fn=initial_load_sequence,
125
  inputs=[url_user_token_display, org_urn_display, token_state],
@@ -127,51 +155,52 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
127
  show_progress="full"
128
  )
129
 
130
- # Original Sync Button Logic
131
  sync_data_btn.click(
132
- fn=lambda: get_sync_animation_html(),
133
  inputs=None,
134
  outputs=[sync_status_html_output],
135
  show_progress=False
136
  ).then(
137
- fn=sync_all_linkedin_data_orchestrator,
138
  inputs=[token_state],
139
  outputs=[sync_status_html_output, token_state],
140
  show_progress=False # Animation is the progress
141
  ).then(
142
- fn=process_and_store_bubble_token,
143
  inputs=[url_user_token_display, org_urn_display, token_state],
144
  outputs=[status_box, token_state, sync_data_btn],
145
  show_progress=False
146
  ).then(
147
- fn=display_main_dashboard,
148
  inputs=[token_state],
149
  outputs=[dashboard_display_html],
150
  show_progress=False
151
  )
152
 
153
- # New Test Animation Button Logic
154
  test_animation_btn.click(
155
- fn=show_animation_for_test,
156
  inputs=None,
157
  outputs=[sync_status_html_output],
158
- show_progress=False # Animation is the progress
159
- ).then(
160
- fn=simulate_processing_after_animation,
161
- inputs=None,
162
- outputs=[sync_status_html_output],
163
- show_progress=False # Message indicates completion
164
  )
165
 
166
-
167
  with gr.TabItem("2️⃣ Analytics"):
168
  fetch_analytics_btn = gr.Button("πŸ“ˆ Fetch/Refresh Full Analytics", variant="primary")
169
- follower_count_md = gr.Markdown("Analytics data will load here...")
170
- with gr.Row(): follower_plot, growth_plot = gr.Plot(label="Follower Demographics"), gr.Plot(label="Follower Growth")
171
- with gr.Row(): eng_rate_plot = gr.Plot(label="Engagement Rate")
172
- with gr.Row(): interaction_plot = gr.Plot(label="Post Interactions")
173
- with gr.Row(): eb_plot = gr.Plot(label="Engagement Benchmark")
174
- with gr.Row(): mentions_vol_plot, mentions_sentiment_plot = gr.Plot(label="Mentions Volume"), gr.Plot(label="Mentions Sentiment")
 
 
 
 
 
 
 
175
 
176
  fetch_analytics_btn.click(
177
  fn=guarded_fetch_analytics, inputs=[token_state],
@@ -184,6 +213,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
184
  refresh_mentions_display_btn = gr.Button("πŸ”„ Refresh Mentions Display (from local data)", variant="secondary")
185
  mentions_html = gr.HTML("Mentions data loads from Bubble after sync. Click refresh to view current local data.")
186
  mentions_sentiment_dist_plot = gr.Plot(label="Mention Sentiment Distribution")
 
187
  refresh_mentions_display_btn.click(
188
  fn=run_mentions_tab_display, inputs=[token_state],
189
  outputs=[mentions_html, mentions_sentiment_dist_plot],
@@ -206,6 +236,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
206
  )
207
 
208
  if __name__ == "__main__":
 
209
  if not os.environ.get(LINKEDIN_CLIENT_ID_ENV_VAR):
210
  logging.warning(f"WARNING: '{LINKEDIN_CLIENT_ID_ENV_VAR}' environment variable not set. The app may not function correctly for LinkedIn API calls.")
211
  if not os.environ.get(BUBBLE_APP_NAME_ENV_VAR) or \
@@ -213,10 +244,12 @@ if __name__ == "__main__":
213
  not os.environ.get(BUBBLE_API_ENDPOINT_ENV_VAR):
214
  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.")
215
 
 
216
  try:
217
  import matplotlib
218
  logging.info(f"Matplotlib version: {matplotlib.__version__} found. Backend: {matplotlib.get_backend()}")
219
  except ImportError:
220
  logging.error("Matplotlib is not installed. Plots will not be generated. Please install it: pip install matplotlib")
221
 
222
- app.launch(server_name="0.0.0.0", server_port=7860, debug=True, share=False)
 
 
32
  """Loads the HTML content for the sync animation."""
33
  try:
34
  # Ensure this path is correct relative to where app.py is run
35
+ # Make sure 'sync_animation.html' is in the same directory as app.py
36
  with open("sync_animation.html", "r", encoding="utf-8") as f:
37
  return f.read()
38
  except FileNotFoundError:
 
49
  logging.warning("Analytics fetch: Access denied. No token.")
50
  return ("❌ Access denied. No token.", None, None, None, None, None, None, None)
51
 
52
+ # Ensure dataframes are at least empty DataFrames if not found in state
53
  posts_df_analytics = token_state.get("bubble_posts_df", pd.DataFrame())
54
  mentions_df_analytics = token_state.get("bubble_mentions_df", pd.DataFrame())
55
  follower_stats_df_analytics = token_state.get("bubble_follower_stats_df", pd.DataFrame())
56
 
57
  logging.info("Calling fetch_and_render_analytics with current token_state data.")
58
  try:
59
+ # Call the actual or placeholder function
60
  return fetch_and_render_analytics(
61
  token_state.get("client_id"),
62
  token_state.get("token"),
 
69
  logging.error(f"Error in guarded_fetch_analytics calling fetch_and_render_analytics: {e}", exc_info=True)
70
  return (f"❌ Error fetching analytics: {e}", None, None, None, None, None, None, None)
71
 
72
+ # --- Animation Test Function (Generator) ---
73
+ def show_animation_then_simulate_processing():
74
+ """
75
+ Yields the animation HTML, then simulates a delay,
76
+ and finally yields a completion message.
77
+ """
78
+ logging.info("TEST BUTTON: Yielding animation HTML.")
79
+ yield get_sync_animation_html() # First update: Display the animation
80
 
81
+ logging.info("TEST BUTTON: Simulating processing (server-side delay of 8 seconds).")
82
+ time.sleep(8) # Server-side delay
83
+
84
+ logging.info("TEST BUTTON: Simulation complete. Yielding completion message.")
85
+ yield "<p style='text-align:center; color: green; font-size: 1.2em;'>βœ… Animation Test Complete!</p>" # Second update
 
86
 
87
 
88
  # --- Gradio UI Blocks ---
 
97
  "bubble_operations_log_df": pd.DataFrame(),
98
  "mentions_should_sync_now": False,
99
  "fs_should_sync_now": False,
100
+ "url_user_token_temp_storage": None # Not used in current logic, but kept from original
101
  })
102
 
103
  gr.Markdown("# πŸš€ LinkedIn Organization Dashboard")
104
+ # These textboxes are used to capture URL parameters via app.load()
105
  url_user_token_display = gr.Textbox(label="User Token (from URL - Hidden)", interactive=False, visible=False)
106
  status_box = gr.Textbox(label="Overall LinkedIn Token Status", interactive=False, value="Initializing...")
107
  org_urn_display = gr.Textbox(label="Organization URN (from URL - Hidden)", interactive=False, visible=False)
108
 
109
+ # This function runs when the Gradio app loads in the browser.
110
+ # It calls `get_url_user_token` which should be JavaScript or a Gradio request handler.
111
+ # For it to work with JavaScript `fn=None, inputs=None, ..., js="() => { ... return [token, urn]; }"
112
+ # For Python function, it needs a gr.Request input.
113
  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)
114
 
115
+ def initial_load_sequence(url_token, org_urn_val, current_state, request: gr.Request): # Added request for get_url_user_token if it needs it
116
+ """Handles the initial processing after URL parameters are potentially loaded."""
117
+ # If get_url_user_token is pure Python and needs request, it's passed here.
118
+ # If get_url_user_token was JS, url_token and org_urn_val would be populated from its output.
119
+
120
+ # If url_token and org_urn_val are NOT populated by app.load (e.g. if get_url_user_token is a JS snippet that didn't run or returned null)
121
+ # you might need to call get_url_user_token here again if it's a Python function that needs the request object.
122
+ # However, app.load is designed to populate its outputs.
123
+ # For this example, we assume url_token and org_urn_val are correctly passed from org_urn_display.change
124
+ # which is triggered after app.load populates org_urn_display.
125
+
126
+ logging.info(f"Initial load sequence triggered. Org URN via change: {org_urn_val}, URL Token via change: {'Present' if url_token else 'Absent'}")
127
+
128
+ # Call state_manager.process_and_store_bubble_token
129
  status_msg, new_state, btn_update = process_and_store_bubble_token(url_token, org_urn_val, current_state)
130
+
131
+ # Call ui_generators.display_main_dashboard
132
+ dashboard_content_html = display_main_dashboard(new_state)
133
+
134
+ return status_msg, new_state, btn_update, dashboard_content_html
135
 
136
  with gr.Tabs():
137
  with gr.TabItem("1️⃣ Dashboard & Sync"):
138
  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.")
139
 
140
+ with gr.Row():
141
  sync_data_btn = gr.Button("πŸ”„ Sync LinkedIn Data", variant="primary", visible=False, interactive=False)
142
+ test_animation_btn = gr.Button("πŸ§ͺ Test Animation Display", variant="secondary")
143
 
144
+ # HTML component to display sync status or animation
145
  sync_status_html_output = gr.HTML("<p style='text-align:center;'>Sync status will appear here.</p>")
146
 
147
+ # HTML component to display the main dashboard content
148
  dashboard_display_html = gr.HTML("<p style='text-align:center;'>Dashboard loading...</p>")
149
 
150
+ # When org_urn_display changes (after app.load), trigger initial_load_sequence
151
  org_urn_display.change(
152
  fn=initial_load_sequence,
153
  inputs=[url_user_token_display, org_urn_display, token_state],
 
155
  show_progress="full"
156
  )
157
 
158
+ # Original Sync Button Logic (multi-step process)
159
  sync_data_btn.click(
160
+ fn=lambda: get_sync_animation_html(), # Show animation first
161
  inputs=None,
162
  outputs=[sync_status_html_output],
163
  show_progress=False
164
  ).then(
165
+ fn=sync_all_linkedin_data_orchestrator, # Perform actual sync
166
  inputs=[token_state],
167
  outputs=[sync_status_html_output, token_state],
168
  show_progress=False # Animation is the progress
169
  ).then(
170
+ fn=process_and_store_bubble_token, # Re-process token/state after sync
171
  inputs=[url_user_token_display, org_urn_display, token_state],
172
  outputs=[status_box, token_state, sync_data_btn],
173
  show_progress=False
174
  ).then(
175
+ fn=display_main_dashboard, # Refresh dashboard display
176
  inputs=[token_state],
177
  outputs=[dashboard_display_html],
178
  show_progress=False
179
  )
180
 
181
+ # New Test Animation Button Logic (uses the generator function)
182
  test_animation_btn.click(
183
+ fn=show_animation_then_simulate_processing,
184
  inputs=None,
185
  outputs=[sync_status_html_output],
186
+ show_progress=False # Animation itself is the progress indicator
 
 
 
 
 
187
  )
188
 
 
189
  with gr.TabItem("2️⃣ Analytics"):
190
  fetch_analytics_btn = gr.Button("πŸ“ˆ Fetch/Refresh Full Analytics", variant="primary")
191
+ follower_count_md = gr.Markdown("Analytics data will load here...") # For HTML content like follower count
192
+ with gr.Row():
193
+ follower_plot = gr.Plot(label="Follower Demographics")
194
+ growth_plot = gr.Plot(label="Follower Growth")
195
+ with gr.Row():
196
+ eng_rate_plot = gr.Plot(label="Engagement Rate")
197
+ with gr.Row():
198
+ interaction_plot = gr.Plot(label="Post Interactions")
199
+ with gr.Row():
200
+ eb_plot = gr.Plot(label="Engagement Benchmark")
201
+ with gr.Row():
202
+ mentions_vol_plot = gr.Plot(label="Mentions Volume")
203
+ mentions_sentiment_plot = gr.Plot(label="Mentions Sentiment")
204
 
205
  fetch_analytics_btn.click(
206
  fn=guarded_fetch_analytics, inputs=[token_state],
 
213
  refresh_mentions_display_btn = gr.Button("πŸ”„ Refresh Mentions Display (from local data)", variant="secondary")
214
  mentions_html = gr.HTML("Mentions data loads from Bubble after sync. Click refresh to view current local data.")
215
  mentions_sentiment_dist_plot = gr.Plot(label="Mention Sentiment Distribution")
216
+
217
  refresh_mentions_display_btn.click(
218
  fn=run_mentions_tab_display, inputs=[token_state],
219
  outputs=[mentions_html, mentions_sentiment_dist_plot],
 
236
  )
237
 
238
  if __name__ == "__main__":
239
+ # Check for environment variables
240
  if not os.environ.get(LINKEDIN_CLIENT_ID_ENV_VAR):
241
  logging.warning(f"WARNING: '{LINKEDIN_CLIENT_ID_ENV_VAR}' environment variable not set. The app may not function correctly for LinkedIn API calls.")
242
  if not os.environ.get(BUBBLE_APP_NAME_ENV_VAR) or \
 
244
  not os.environ.get(BUBBLE_API_ENDPOINT_ENV_VAR):
245
  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.")
246
 
247
+ # Check for Matplotlib (optional, but good for plots)
248
  try:
249
  import matplotlib
250
  logging.info(f"Matplotlib version: {matplotlib.__version__} found. Backend: {matplotlib.get_backend()}")
251
  except ImportError:
252
  logging.error("Matplotlib is not installed. Plots will not be generated. Please install it: pip install matplotlib")
253
 
254
+ # Launch the Gradio app
255
+ app.launch(server_name="0.0.0.0", server_port=7860, debug=True, share=False)