Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,88 +1,195 @@
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
import gradio as gr
|
| 3 |
import json
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
# Assuming these custom modules exist in your project directory or Python path
|
| 5 |
from Data_Fetching_and_Rendering import fetch_and_render_dashboard
|
| 6 |
from analytics_fetch_and_rendering import fetch_and_render_analytics
|
| 7 |
from mentions_dashboard import generate_mentions_dashboard
|
| 8 |
|
| 9 |
-
from
|
|
|
|
| 10 |
|
| 11 |
-
# Shared state for token received via POST
|
| 12 |
token_received = {"status": False, "token": None, "client_id": None}
|
| 13 |
|
| 14 |
-
|
| 15 |
# --- Handlers for token reception (POST) and status ---
|
| 16 |
def receive_token(accessToken: str, client_id: str):
|
| 17 |
"""
|
| 18 |
Called by a hidden POST mechanism to supply the OAuth code/token and client ID.
|
| 19 |
"""
|
| 20 |
try:
|
| 21 |
-
# The .replace("'", '"') is kept from your original code.
|
| 22 |
-
# Be cautious if accessToken format can vary.
|
| 23 |
token_dict = json.loads(accessToken.replace("'", '"'))
|
| 24 |
except json.JSONDecodeError as e:
|
| 25 |
-
print(f"Error decoding accessToken: {e}")
|
| 26 |
-
token_received["status"] = False
|
| 27 |
token_received["token"] = None
|
| 28 |
-
token_received["client_id"] = client_id
|
| 29 |
-
return
|
| 30 |
-
"status": "β Invalid token format (POST)",
|
| 31 |
-
"token": "",
|
| 32 |
-
"client_id": client_id
|
| 33 |
-
}
|
| 34 |
|
| 35 |
token_received["status"] = True
|
| 36 |
-
token_received["token"] = token_dict
|
| 37 |
token_received["client_id"] = client_id
|
| 38 |
print(f"Token (from POST) received successfully. Client ID: {client_id}")
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
"client_id": client_id
|
| 43 |
-
}
|
| 44 |
|
| 45 |
def check_status():
|
| 46 |
-
return "β
Token
|
| 47 |
|
| 48 |
-
def show_token(): # Shows
|
| 49 |
-
if token_received["status"] and token_received["token"]:
|
| 50 |
-
return token_received["token"].get("access_token", "Access token key missing")
|
|
|
|
|
|
|
| 51 |
return ""
|
| 52 |
|
| 53 |
-
def show_client():
|
| 54 |
return token_received["client_id"] if token_received["status"] and token_received["client_id"] else ""
|
| 55 |
|
| 56 |
-
# ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
def guarded_fetch_dashboard():
|
| 58 |
if not token_received["status"]:
|
| 59 |
-
return "<p style='color:red; text-align:center;'>β Access denied. No token
|
| 60 |
-
# token_received["client_id"] and token_received["token"] required by fetch function
|
| 61 |
html = fetch_and_render_dashboard(
|
| 62 |
token_received["client_id"],
|
| 63 |
-
token_received["token"]
|
| 64 |
)
|
| 65 |
return html
|
| 66 |
|
| 67 |
def guarded_fetch_analytics():
|
| 68 |
if not token_received["status"]:
|
| 69 |
return (
|
| 70 |
-
"<p style='color:red; text-align:center;'>β Access denied. No token
|
| 71 |
-
None, None, None, None, None, None, None
|
| 72 |
)
|
| 73 |
-
# Assuming fetch_and_render_analytics returns 8 values
|
| 74 |
count_md, plot, growth_plot, avg_post_eng_rate, interaction_metrics, eb_metrics, mentions_vol_metrics, mentions_sentiment_metrics = fetch_and_render_analytics(
|
| 75 |
token_received["client_id"],
|
| 76 |
-
token_received["token"]
|
| 77 |
)
|
| 78 |
return count_md, plot, growth_plot, avg_post_eng_rate, interaction_metrics, eb_metrics, mentions_vol_metrics, mentions_sentiment_metrics
|
| 79 |
|
| 80 |
def run_mentions_and_load():
|
| 81 |
-
if not token_received["status"]:
|
| 82 |
-
return ("<p style='color:red; text-align:center;'>β Access denied. No token
|
| 83 |
html, fig = generate_mentions_dashboard(
|
| 84 |
token_received["client_id"],
|
| 85 |
-
token_received["token"]
|
| 86 |
)
|
| 87 |
return html, fig
|
| 88 |
|
|
@@ -90,46 +197,51 @@ def run_mentions_and_load():
|
|
| 90 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
| 91 |
title="LinkedIn Post Viewer & Analytics") as app:
|
| 92 |
gr.Markdown("# π LinkedIn Organization Post Viewer & Analytics")
|
| 93 |
-
gr.Markdown("
|
| 94 |
|
| 95 |
# Hidden elements: simulate POST endpoint for OAuth token
|
| 96 |
-
|
| 97 |
-
|
| 98 |
hidden_btn = gr.Button(visible=False, elem_id="hidden_btn")
|
| 99 |
|
| 100 |
# --- Display elements ---
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
-
|
| 105 |
-
token_display = gr.Textbox(label="Access Token (from POST)", interactive=False)
|
| 106 |
-
client_display = gr.Textbox(label="Client ID (from POST)", interactive=False)
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
|
|
|
|
|
|
| 111 |
app.load(
|
| 112 |
-
fn=get_url_user_token,
|
| 113 |
-
inputs=None,
|
| 114 |
-
outputs=[url_user_token_display]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
)
|
| 116 |
|
| 117 |
-
# Wire hidden POST handler for OAuth token
|
| 118 |
hidden_btn.click(
|
| 119 |
fn=receive_token,
|
| 120 |
-
inputs=[
|
| 121 |
-
outputs=[status_box, token_display, client_display]
|
| 122 |
)
|
| 123 |
-
|
| 124 |
-
# Polling timer to update status and displays for the POSTed token
|
| 125 |
-
# Initial values are set by app.load for status_box, token_display, client_display
|
| 126 |
-
# then updated by timer ticks or hidden_btn click.
|
| 127 |
-
# We call check_status, show_token, show_client once at load time and then via timer.
|
| 128 |
app.load(fn=check_status, outputs=status_box)
|
| 129 |
app.load(fn=show_token, outputs=token_display)
|
| 130 |
app.load(fn=show_client, outputs=client_display)
|
| 131 |
|
| 132 |
-
timer = gr.Timer(
|
| 133 |
timer.tick(fn=check_status, outputs=status_box)
|
| 134 |
timer.tick(fn=show_token, outputs=token_display)
|
| 135 |
timer.tick(fn=show_client, outputs=client_display)
|
|
@@ -139,7 +251,7 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
| 139 |
with gr.TabItem("1οΈβ£ Dashboard"):
|
| 140 |
gr.Markdown("View your organization's recent posts and their engagement statistics.")
|
| 141 |
fetch_dashboard_btn = gr.Button("π Fetch Posts & Stats", variant="primary")
|
| 142 |
-
dashboard_html = gr.HTML(value="<p style='text-align: center; color: #555;'>Waiting for
|
| 143 |
fetch_dashboard_btn.click(
|
| 144 |
fn=guarded_fetch_dashboard,
|
| 145 |
inputs=[],
|
|
@@ -150,38 +262,36 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
| 150 |
gr.Markdown("View follower count and monthly gains for your organization.")
|
| 151 |
fetch_analytics_btn = gr.Button("π Fetch Follower Analytics", variant="primary")
|
| 152 |
|
| 153 |
-
follower_count = gr.Markdown("<p style='text-align: center; color: #555;'>Waiting for
|
| 154 |
|
| 155 |
with gr.Row():
|
| 156 |
-
follower_plot = gr.Plot(visible=True)
|
| 157 |
-
growth_rate_plot = gr.Plot(visible=True)
|
| 158 |
|
| 159 |
with gr.Row():
|
| 160 |
-
post_eng_rate_plot = gr.Plot(visible=True)
|
| 161 |
|
| 162 |
with gr.Row():
|
| 163 |
-
interaction_data = gr.Plot(visible=True)
|
| 164 |
|
| 165 |
with gr.Row():
|
| 166 |
-
eb_data = gr.Plot(visible=True)
|
| 167 |
|
| 168 |
with gr.Row():
|
| 169 |
-
mentions_vol_data = gr.Plot(visible=True)
|
| 170 |
-
mentions_sentiment_data = gr.Plot(visible=True)
|
| 171 |
|
| 172 |
fetch_analytics_btn.click(
|
| 173 |
fn=guarded_fetch_analytics,
|
| 174 |
inputs=[],
|
| 175 |
-
outputs=[follower_count, follower_plot, growth_rate_plot, post_eng_rate_plot, interaction_data, eb_data, mentions_vol_data, mentions_sentiment_data]
|
| 176 |
-
# Show plots after click; they might need to be initially invisible if fetch_and_render_analytics can return None for plots on error
|
| 177 |
-
# For simplicity, keeping them visible. Handle None returns in your fetch function if necessary.
|
| 178 |
)
|
| 179 |
|
| 180 |
with gr.TabItem("3οΈβ£ Mentions"):
|
| 181 |
gr.Markdown("Analyze sentiment of recent posts that mention your organization.")
|
| 182 |
fetch_mentions_btn = gr.Button("π§ Fetch Mentions & Sentiment", variant="primary")
|
| 183 |
-
mentions_html = gr.HTML(value="<p style='text-align: center; color: #555;'>Waiting for
|
| 184 |
-
mentions_plot = gr.Plot(visible=True)
|
| 185 |
fetch_mentions_btn.click(
|
| 186 |
fn=run_mentions_and_load,
|
| 187 |
inputs=[],
|
|
@@ -190,7 +300,8 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
|
| 190 |
|
| 191 |
# Launch the app
|
| 192 |
if __name__ == "__main__":
|
| 193 |
-
#
|
| 194 |
-
# For
|
|
|
|
|
|
|
| 195 |
app.launch(server_name="0.0.0.0", server_port=7860, share=True)
|
| 196 |
-
|
|
|
|
| 1 |
# -*- coding: utf-8 -*-
|
| 2 |
import gradio as gr
|
| 3 |
import json
|
| 4 |
+
import requests # Added for API calls
|
| 5 |
+
import os # Added for environment variables
|
| 6 |
+
import urllib.parse # Added for URL encoding (though requests handles params well)
|
| 7 |
+
|
| 8 |
# Assuming these custom modules exist in your project directory or Python path
|
| 9 |
from Data_Fetching_and_Rendering import fetch_and_render_dashboard
|
| 10 |
from analytics_fetch_and_rendering import fetch_and_render_analytics
|
| 11 |
from mentions_dashboard import generate_mentions_dashboard
|
| 12 |
|
| 13 |
+
# Import the function from your utils file
|
| 14 |
+
from gradio_utils import get_url_user_token # Assuming gradio_utils.py is in the same directory
|
| 15 |
|
| 16 |
+
# Shared state for token received via POST or Bubble
|
| 17 |
token_received = {"status": False, "token": None, "client_id": None}
|
| 18 |
|
|
|
|
| 19 |
# --- Handlers for token reception (POST) and status ---
|
| 20 |
def receive_token(accessToken: str, client_id: str):
|
| 21 |
"""
|
| 22 |
Called by a hidden POST mechanism to supply the OAuth code/token and client ID.
|
| 23 |
"""
|
| 24 |
try:
|
|
|
|
|
|
|
| 25 |
token_dict = json.loads(accessToken.replace("'", '"'))
|
| 26 |
except json.JSONDecodeError as e:
|
| 27 |
+
print(f"Error decoding accessToken (POST): {e}")
|
| 28 |
+
token_received["status"] = False
|
| 29 |
token_received["token"] = None
|
| 30 |
+
token_received["client_id"] = client_id
|
| 31 |
+
return "β Invalid token format (POST)", "", client_id
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
token_received["status"] = True
|
| 34 |
+
token_received["token"] = token_dict # This should be the dict like {"access_token": "value"}
|
| 35 |
token_received["client_id"] = client_id
|
| 36 |
print(f"Token (from POST) received successfully. Client ID: {client_id}")
|
| 37 |
+
# Update status box, token display, client display directly
|
| 38 |
+
return check_status(), show_token(), show_client()
|
| 39 |
+
|
|
|
|
|
|
|
| 40 |
|
| 41 |
def check_status():
|
| 42 |
+
return "β
Token available" if token_received["status"] else "β Waiting for tokenβ¦"
|
| 43 |
|
| 44 |
+
def show_token(): # Shows access_token if available
|
| 45 |
+
if token_received["status"] and token_received["token"] and isinstance(token_received["token"], dict):
|
| 46 |
+
return token_received["token"].get("access_token", "Access token key missing in dict")
|
| 47 |
+
elif token_received["status"] and token_received["token"]: # If token is a raw string (should not happen with new logic)
|
| 48 |
+
return str(token_received["token"]) # Fallback, but ideally token_received["token"] is always a dict if status is True
|
| 49 |
return ""
|
| 50 |
|
| 51 |
+
def show_client():
|
| 52 |
return token_received["client_id"] if token_received["status"] and token_received["client_id"] else ""
|
| 53 |
|
| 54 |
+
# --- Function to fetch LinkedIn Token from Bubble.io ---
|
| 55 |
+
def fetch_linkedin_token_from_bubble(url_user_token_str):
|
| 56 |
+
"""
|
| 57 |
+
Fetches LinkedIn access token from Bubble.io API using the state value (url_user_token_str).
|
| 58 |
+
The token is expected in a 'Raw_text' field as a JSON string, which is then parsed.
|
| 59 |
+
Updates the global token_received state if successful.
|
| 60 |
+
Returns status messages for UI update.
|
| 61 |
+
"""
|
| 62 |
+
# Initial UI states (in case of early exit or error)
|
| 63 |
+
current_status = check_status()
|
| 64 |
+
current_token_display = show_token()
|
| 65 |
+
current_client_display = show_client()
|
| 66 |
+
|
| 67 |
+
bubble_api_key = os.environ.get("Bubble_API")
|
| 68 |
+
if not bubble_api_key:
|
| 69 |
+
error_msg = "β Bubble API Error: The 'Bubble_API' environment variable is not set."
|
| 70 |
+
print(error_msg)
|
| 71 |
+
return error_msg, current_status, current_token_display, current_client_display
|
| 72 |
+
|
| 73 |
+
if not url_user_token_str or "not found" in url_user_token_str or "Could not access" in url_user_token_str:
|
| 74 |
+
return f"βΉοΈ No valid user token from URL to query Bubble. ({url_user_token_str})", current_status, current_token_display, current_client_display
|
| 75 |
+
|
| 76 |
+
base_url = "https://app.ingaze.ai/version-test/api/1.1/obj/Linkedin_access"
|
| 77 |
+
constraints = [{"key": "state", "constraint_type": "equals", "value": url_user_token_str}]
|
| 78 |
+
params = {'constraints': json.dumps(constraints)}
|
| 79 |
+
headers = {"Authorization": f"Bearer {bubble_api_key}"}
|
| 80 |
+
|
| 81 |
+
bubble_api_status_msg = f"Attempting to fetch token from Bubble for state: {url_user_token_str}..."
|
| 82 |
+
print(bubble_api_status_msg)
|
| 83 |
+
|
| 84 |
+
response = None
|
| 85 |
+
try:
|
| 86 |
+
response = requests.get(base_url, params=params, headers=headers, timeout=15) # Increased timeout slightly
|
| 87 |
+
response.raise_for_status()
|
| 88 |
+
|
| 89 |
+
data = response.json()
|
| 90 |
+
results = data.get("response", {}).get("results", [])
|
| 91 |
+
|
| 92 |
+
if results:
|
| 93 |
+
raw_text_from_bubble = results[0].get("Raw_text", None)
|
| 94 |
+
parsed_token_dict = None
|
| 95 |
+
|
| 96 |
+
if raw_text_from_bubble and isinstance(raw_text_from_bubble, str):
|
| 97 |
+
try:
|
| 98 |
+
parsed_token_dict = json.loads(raw_text_from_bubble)
|
| 99 |
+
if not isinstance(parsed_token_dict, dict):
|
| 100 |
+
bubble_api_status_msg = (f"β οΈ Bubble API: 'Raw_text' field did not contain a valid JSON dictionary string. "
|
| 101 |
+
f"Content type: {type(raw_text_from_bubble)}, Value: {raw_text_from_bubble}")
|
| 102 |
+
print(bubble_api_status_msg)
|
| 103 |
+
parsed_token_dict = None
|
| 104 |
+
# If it is a dict, parsed_token_dict is now the token dictionary itself
|
| 105 |
+
except json.JSONDecodeError as e:
|
| 106 |
+
bubble_api_status_msg = (f"β οΈ Bubble API: Error decoding 'Raw_text' JSON string: {e}. "
|
| 107 |
+
f"Content: {raw_text_from_bubble}")
|
| 108 |
+
print(bubble_api_status_msg)
|
| 109 |
+
parsed_token_dict = None
|
| 110 |
+
elif raw_text_from_bubble: # It exists but is not a string
|
| 111 |
+
bubble_api_status_msg = (f"β οΈ Bubble API: 'Raw_text' field was not a string. "
|
| 112 |
+
f"Type: {type(raw_text_from_bubble)}, Value: {raw_text_from_bubble}")
|
| 113 |
+
print(bubble_api_status_msg)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
if parsed_token_dict and "access_token" in parsed_token_dict:
|
| 117 |
+
token_received["status"] = True
|
| 118 |
+
token_received["token"] = parsed_token_dict # Store the entire parsed dictionary
|
| 119 |
+
token_received["client_id"] = f"Bubble (state: {url_user_token_str})"
|
| 120 |
+
bubble_api_status_msg = f"β
LinkedIn Token successfully fetched and parsed from Bubble 'Raw_text' for state: {url_user_token_str}"
|
| 121 |
+
print(bubble_api_status_msg)
|
| 122 |
+
elif raw_text_from_bubble and not parsed_token_dict:
|
| 123 |
+
# Error message already set by parsing logic if raw_text_from_bubble existed but parsing failed.
|
| 124 |
+
# If bubble_api_status_msg wasn't set by specific parsing errors, use a general one.
|
| 125 |
+
if not bubble_api_status_msg.startswith("β οΈ"): # Avoid overwriting specific parsing error
|
| 126 |
+
bubble_api_status_msg = f"β οΈ Bubble API: 'Raw_text' found but could not be parsed into a valid token dictionary for state: {url_user_token_str}."
|
| 127 |
+
print(bubble_api_status_msg)
|
| 128 |
+
elif not raw_text_from_bubble:
|
| 129 |
+
bubble_api_status_msg = (f"β οΈ Bubble API: Token field ('Raw_text') "
|
| 130 |
+
f"not found or is null in response for state: {url_user_token_str}. Result: {results[0]}")
|
| 131 |
+
print(bubble_api_status_msg)
|
| 132 |
+
elif parsed_token_dict and "access_token" not in parsed_token_dict: # Parsed OK, but missing the crucial key
|
| 133 |
+
bubble_api_status_msg = (f"β οΈ Bubble API: 'access_token' key missing in parsed 'Raw_text' dictionary for state: {url_user_token_str}. Parsed: {parsed_token_dict}")
|
| 134 |
+
print(bubble_api_status_msg)
|
| 135 |
+
# If none of the above, the initial bubble_api_status_msg will be used or an error below will catch it.
|
| 136 |
+
|
| 137 |
+
else: # No results from Bubble for the given state
|
| 138 |
+
bubble_api_status_msg = f"β Bubble API: No results found for state: {url_user_token_str}"
|
| 139 |
+
print(bubble_api_status_msg)
|
| 140 |
+
|
| 141 |
+
except requests.exceptions.HTTPError as http_err:
|
| 142 |
+
error_details = response.text if response else "No response content"
|
| 143 |
+
bubble_api_status_msg = f"β Bubble API HTTP error: {http_err} - Response: {error_details}"
|
| 144 |
+
print(bubble_api_status_msg)
|
| 145 |
+
except requests.exceptions.Timeout:
|
| 146 |
+
bubble_api_status_msg = "β Bubble API Request timed out."
|
| 147 |
+
print(bubble_api_status_msg)
|
| 148 |
+
except requests.exceptions.RequestException as req_err:
|
| 149 |
+
bubble_api_status_msg = f"β Bubble API Request error: {req_err}"
|
| 150 |
+
print(bubble_api_status_msg)
|
| 151 |
+
except json.JSONDecodeError as json_err: # Error decoding the main Bubble response, not Raw_text
|
| 152 |
+
error_details = response.text if response else "No response content"
|
| 153 |
+
bubble_api_status_msg = f"β Bubble API main response JSON decode error: {json_err}. Response: {error_details}"
|
| 154 |
+
print(bubble_api_status_msg)
|
| 155 |
+
except Exception as e:
|
| 156 |
+
bubble_api_status_msg = f"β An unexpected error occurred while fetching from Bubble: {str(e)}"
|
| 157 |
+
print(bubble_api_status_msg)
|
| 158 |
+
|
| 159 |
+
# Return values to update all relevant UI components
|
| 160 |
+
return bubble_api_status_msg, check_status(), show_token(), show_client()
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
# --- Guarded fetch functions (using token from POST or Bubble) ---
|
| 164 |
+
# These functions expect token_received["token"] to be a dictionary
|
| 165 |
+
# like {"access_token": "actual_token_value", ...}
|
| 166 |
def guarded_fetch_dashboard():
|
| 167 |
if not token_received["status"]:
|
| 168 |
+
return "<p style='color:red; text-align:center;'>β Access denied. No token available. Please send token first or ensure URL token is valid.</p>"
|
|
|
|
| 169 |
html = fetch_and_render_dashboard(
|
| 170 |
token_received["client_id"],
|
| 171 |
+
token_received["token"]
|
| 172 |
)
|
| 173 |
return html
|
| 174 |
|
| 175 |
def guarded_fetch_analytics():
|
| 176 |
if not token_received["status"]:
|
| 177 |
return (
|
| 178 |
+
"<p style='color:red; text-align:center;'>β Access denied. No token available.</p>",
|
| 179 |
+
None, None, None, None, None, None, None
|
| 180 |
)
|
|
|
|
| 181 |
count_md, plot, growth_plot, avg_post_eng_rate, interaction_metrics, eb_metrics, mentions_vol_metrics, mentions_sentiment_metrics = fetch_and_render_analytics(
|
| 182 |
token_received["client_id"],
|
| 183 |
+
token_received["token"]
|
| 184 |
)
|
| 185 |
return count_md, plot, growth_plot, avg_post_eng_rate, interaction_metrics, eb_metrics, mentions_vol_metrics, mentions_sentiment_metrics
|
| 186 |
|
| 187 |
def run_mentions_and_load():
|
| 188 |
+
if not token_received["status"]:
|
| 189 |
+
return ("<p style='color:red; text-align:center;'>β Access denied. No token available.</p>", None)
|
| 190 |
html, fig = generate_mentions_dashboard(
|
| 191 |
token_received["client_id"],
|
| 192 |
+
token_received["token"]
|
| 193 |
)
|
| 194 |
return html, fig
|
| 195 |
|
|
|
|
| 197 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="sky"),
|
| 198 |
title="LinkedIn Post Viewer & Analytics") as app:
|
| 199 |
gr.Markdown("# π LinkedIn Organization Post Viewer & Analytics")
|
| 200 |
+
gr.Markdown("Token can be supplied via URL parameter (for Bubble.io lookup) or hidden POST. Then explore dashboard and analytics.")
|
| 201 |
|
| 202 |
# Hidden elements: simulate POST endpoint for OAuth token
|
| 203 |
+
hidden_token_input = gr.Textbox(visible=False, elem_id="hidden_token")
|
| 204 |
+
hidden_client_input = gr.Textbox(visible=False, elem_id="hidden_client_id")
|
| 205 |
hidden_btn = gr.Button(visible=False, elem_id="hidden_btn")
|
| 206 |
|
| 207 |
# --- Display elements ---
|
| 208 |
+
url_user_token_display = gr.Textbox(
|
| 209 |
+
label="User Token (from URL - Hidden)",
|
| 210 |
+
interactive=False,
|
| 211 |
+
placeholder="Attempting to load from URL...",
|
| 212 |
+
visible=False
|
| 213 |
+
)
|
| 214 |
|
| 215 |
+
bubble_status_display = gr.Textbox(label="Bubble API Call Status", interactive=False, placeholder="Waiting for URL token...")
|
|
|
|
|
|
|
| 216 |
|
| 217 |
+
status_box = gr.Textbox(label="Overall Token Status", interactive=False)
|
| 218 |
+
token_display = gr.Textbox(label="Access Token (Active)", interactive=False)
|
| 219 |
+
client_display = gr.Textbox(label="Client ID (Active)", interactive=False)
|
| 220 |
+
|
| 221 |
+
# --- Load URL parameter on app start & Link to Bubble Fetch ---
|
| 222 |
app.load(
|
| 223 |
+
fn=get_url_user_token,
|
| 224 |
+
inputs=None,
|
| 225 |
+
outputs=[url_user_token_display]
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
url_user_token_display.change(
|
| 229 |
+
fn=fetch_linkedin_token_from_bubble,
|
| 230 |
+
inputs=[url_user_token_display],
|
| 231 |
+
outputs=[bubble_status_display, status_box, token_display, client_display]
|
| 232 |
)
|
| 233 |
|
|
|
|
| 234 |
hidden_btn.click(
|
| 235 |
fn=receive_token,
|
| 236 |
+
inputs=[hidden_token_input, hidden_client_input],
|
| 237 |
+
outputs=[status_box, token_display, client_display]
|
| 238 |
)
|
| 239 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
app.load(fn=check_status, outputs=status_box)
|
| 241 |
app.load(fn=show_token, outputs=token_display)
|
| 242 |
app.load(fn=show_client, outputs=client_display)
|
| 243 |
|
| 244 |
+
timer = gr.Timer(2.0)
|
| 245 |
timer.tick(fn=check_status, outputs=status_box)
|
| 246 |
timer.tick(fn=show_token, outputs=token_display)
|
| 247 |
timer.tick(fn=show_client, outputs=client_display)
|
|
|
|
| 251 |
with gr.TabItem("1οΈβ£ Dashboard"):
|
| 252 |
gr.Markdown("View your organization's recent posts and their engagement statistics.")
|
| 253 |
fetch_dashboard_btn = gr.Button("π Fetch Posts & Stats", variant="primary")
|
| 254 |
+
dashboard_html = gr.HTML(value="<p style='text-align: center; color: #555;'>Waiting for token...</p>")
|
| 255 |
fetch_dashboard_btn.click(
|
| 256 |
fn=guarded_fetch_dashboard,
|
| 257 |
inputs=[],
|
|
|
|
| 262 |
gr.Markdown("View follower count and monthly gains for your organization.")
|
| 263 |
fetch_analytics_btn = gr.Button("π Fetch Follower Analytics", variant="primary")
|
| 264 |
|
| 265 |
+
follower_count = gr.Markdown("<p style='text-align: center; color: #555;'>Waiting for token...</p>")
|
| 266 |
|
| 267 |
with gr.Row():
|
| 268 |
+
follower_plot = gr.Plot(visible=True)
|
| 269 |
+
growth_rate_plot = gr.Plot(visible=True)
|
| 270 |
|
| 271 |
with gr.Row():
|
| 272 |
+
post_eng_rate_plot = gr.Plot(visible=True)
|
| 273 |
|
| 274 |
with gr.Row():
|
| 275 |
+
interaction_data = gr.Plot(visible=True)
|
| 276 |
|
| 277 |
with gr.Row():
|
| 278 |
+
eb_data = gr.Plot(visible=True)
|
| 279 |
|
| 280 |
with gr.Row():
|
| 281 |
+
mentions_vol_data = gr.Plot(visible=True)
|
| 282 |
+
mentions_sentiment_data = gr.Plot(visible=True)
|
| 283 |
|
| 284 |
fetch_analytics_btn.click(
|
| 285 |
fn=guarded_fetch_analytics,
|
| 286 |
inputs=[],
|
| 287 |
+
outputs=[follower_count, follower_plot, growth_rate_plot, post_eng_rate_plot, interaction_data, eb_data, mentions_vol_data, mentions_sentiment_data]
|
|
|
|
|
|
|
| 288 |
)
|
| 289 |
|
| 290 |
with gr.TabItem("3οΈβ£ Mentions"):
|
| 291 |
gr.Markdown("Analyze sentiment of recent posts that mention your organization.")
|
| 292 |
fetch_mentions_btn = gr.Button("π§ Fetch Mentions & Sentiment", variant="primary")
|
| 293 |
+
mentions_html = gr.HTML(value="<p style='text-align: center; color: #555;'>Waiting for token...</p>")
|
| 294 |
+
mentions_plot = gr.Plot(visible=True)
|
| 295 |
fetch_mentions_btn.click(
|
| 296 |
fn=run_mentions_and_load,
|
| 297 |
inputs=[],
|
|
|
|
| 300 |
|
| 301 |
# Launch the app
|
| 302 |
if __name__ == "__main__":
|
| 303 |
+
# Ensure the 'Bubble_API' environment variable is set where this app is run.
|
| 304 |
+
# For local testing, you can set it in your terminal before running:
|
| 305 |
+
# export Bubble_API="YOUR_ACTUAL_BUBBLE_API_KEY"
|
| 306 |
+
# python app.py
|
| 307 |
app.launch(server_name="0.0.0.0", server_port=7860, share=True)
|
|
|