Spaces:
Running
Running
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)
|
|