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