Spaces:
Running
Running
import gradio as gr | |
import pandas as pd # Needed for state variables that hold DataFrames | |
from typing import Dict, Any, Callable | |
# Custom CSS for professional, modern UI | |
CUSTOM_CSS = """ | |
/* === ROOT VARIABLES === */ | |
:root { | |
--primary-color: #0A66C2; | |
--primary-hover: #004182; | |
--secondary-color: #5E9ED6; | |
--accent-color: #F3F2EF; | |
--success-color: #57C4A3; | |
--warning-color: #F5B800; | |
--error-color: #CC1016; | |
--text-primary: #000000DE; | |
--text-secondary: #00000099; | |
--text-tertiary: #00000061; | |
--background-primary: #FFFFFF; | |
--background-secondary: #F8F9FA; | |
--background-tertiary: #F3F2EF; | |
--border-color: #E5E5E5; | |
--border-radius: 12px; | |
--shadow-light: 0 2px 8px rgba(0, 0, 0, 0.08); | |
--shadow-medium: 0 4px 16px rgba(0, 0, 0, 0.12); | |
--shadow-heavy: 0 8px 32px rgba(0, 0, 0, 0.16); | |
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
} | |
/* === GLOBAL STYLES === */ | |
.gradio-container { | |
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%) !important; | |
min-height: 100vh; | |
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; | |
} | |
.gradio-container .main { | |
background: transparent; | |
padding: 0; | |
max-width: none !important; | |
} | |
/* === MAIN HEADER === */ | |
.main-header { | |
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); | |
color: white; | |
padding: 2rem 0; | |
margin: -1rem -1rem 2rem -1rem; | |
text-align: center; | |
box-shadow: var(--shadow-medium); | |
position: relative; | |
overflow: hidden; | |
} | |
.main-header::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.1)" stroke-width="0.5"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>'); | |
opacity: 0.3; | |
} | |
.main-header h1 { | |
font-size: 2.5rem !important; | |
font-weight: 700 !important; | |
margin: 0 !important; | |
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); | |
position: relative; | |
z-index: 1; | |
} | |
.main-header .subtitle { | |
font-size: 1.1rem; | |
opacity: 0.9; | |
margin-top: 0.5rem; | |
position: relative; | |
z-index: 1; | |
} | |
/* === STATUS BAR === */ | |
.status-container { | |
background: var(--background-primary); | |
border-radius: var(--border-radius); | |
padding: 1rem; | |
margin-bottom: 1.5rem; | |
box-shadow: var(--shadow-light); | |
border-left: 4px solid var(--primary-color); | |
} | |
/* === TAB CONTAINER === */ | |
.tab-nav { | |
background: var(--background-primary); | |
border-radius: var(--border-radius); | |
padding: 0.5rem; | |
box-shadow: var(--shadow-light); | |
margin-bottom: 1.5rem; | |
border: none; | |
} | |
.tab-nav button { | |
background: transparent !important; | |
border: none !important; | |
padding: 1rem 1.5rem !important; | |
margin: 0 0.25rem !important; | |
border-radius: 8px !important; | |
font-weight: 600 !important; | |
font-size: 0.95rem !important; | |
transition: var(--transition) !important; | |
color: var(--text-secondary) !important; | |
position: relative; | |
} | |
.tab-nav button:hover { | |
background: var(--background-secondary) !important; | |
color: var(--text-primary) !important; | |
transform: translateY(-2px); | |
} | |
.tab-nav button.selected { | |
background: var(--primary-color) !important; | |
color: white !important; | |
box-shadow: var(--shadow-medium); | |
} | |
/* === TAB CONTENT === */ | |
.tabitem { | |
background: var(--background-primary); | |
border-radius: var(--border-radius); | |
padding: 2rem; | |
box-shadow: var(--shadow-light); | |
border: 1px solid var(--border-color); | |
margin-top: 0; | |
} | |
/* === FORM ELEMENTS === */ | |
.gr-form { | |
background: var(--background-primary); | |
border-radius: var(--border-radius); | |
padding: 1.5rem; | |
box-shadow: var(--shadow-light); | |
border: 1px solid var(--border-color); | |
} | |
input, textarea, select { | |
border: 2px solid var(--border-color) !important; | |
border-radius: 8px !important; | |
padding: 0.75rem 1rem !important; | |
font-size: 0.95rem !important; | |
transition: var(--transition) !important; | |
background: var(--background-primary) !important; | |
} | |
input:focus, textarea:focus, select:focus { | |
border-color: var(--primary-color) !important; | |
box-shadow: 0 0 0 3px rgba(10, 102, 194, 0.1) !important; | |
outline: none !important; | |
} | |
/* === BUTTONS === */ | |
.gr-button { | |
background: var(--primary-color) !important; | |
color: white !important; | |
border: none !important; | |
border-radius: 8px !important; | |
padding: 0.75rem 1.5rem !important; | |
font-weight: 600 !important; | |
font-size: 0.95rem !important; | |
transition: var(--transition) !important; | |
cursor: pointer !important; | |
box-shadow: var(--shadow-light); | |
} | |
.gr-button:hover { | |
background: var(--primary-hover) !important; | |
transform: translateY(-2px); | |
box-shadow: var(--shadow-medium); | |
} | |
.gr-button.secondary { | |
background: var(--background-secondary) !important; | |
color: var(--text-primary) !important; | |
border: 2px solid var(--border-color) !important; | |
} | |
.gr-button.secondary:hover { | |
background: var(--background-tertiary) !important; | |
border-color: var(--primary-color) !important; | |
} | |
/* === CARDS === */ | |
.card { | |
background: var(--background-primary); | |
border-radius: var(--border-radius); | |
padding: 1.5rem; | |
box-shadow: var(--shadow-light); | |
border: 1px solid var(--border-color); | |
transition: var(--transition); | |
margin-bottom: 1rem; | |
} | |
.card:hover { | |
box_shadow: var(--shadow-medium); | |
transform: translateY(-2px); | |
} | |
.card-header { | |
font-size: 1.25rem; | |
font-weight: 700; | |
color: var(--text-primary); | |
margin-bottom: 0.5rem; | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
} | |
.card-content { | |
color: var(--text-secondary); | |
line-height: 1.6; | |
} | |
/* === CHARTS AND PLOTS === */ | |
.plot-container { | |
background: var(--background-primary); | |
border-radius: var(--border-radius); | |
padding: 1rem; | |
box-shadow: var(--shadow-light); | |
border: 1px solid var(--border-color); | |
margin: 1rem 0; | |
} | |
/* === MARKDOWN CONTENT === */ | |
.markdown-content h1, | |
.markdown-content h2, | |
.markdown-content h3 { | |
color: var(--text-primary); | |
font-weight: 700; | |
margin-top: 1.5rem; | |
margin-bottom: 0.75rem; | |
} | |
.markdown-content h1 { | |
font-size: 2rem; | |
border-bottom: 3px solid var(--primary-color); | |
padding-bottom: 0.5rem; | |
} | |
.markdown-content h2 { | |
font-size: 1.5rem; | |
color: var(--primary-color); | |
} | |
.markdown-content h3 { | |
font-size: 1.25rem; | |
color: var(--secondary-color); | |
} | |
.markdown-content p { | |
line-height: 1.7; | |
color: var(--text-secondary); | |
margin-bottom: 1rem; | |
} | |
.markdown-content ul, .markdown-content ol { | |
padding-left: 1.5rem; | |
margin-bottom: 1rem; | |
} | |
.markdown-content li { | |
margin-bottom: 0.5rem; | |
line-height: 1.6; | |
color: var(--text-secondary); | |
} | |
.markdown-content code { | |
background: var(--background-tertiary); | |
padding: 0.2rem 0.4rem; | |
border-radius: 4px; | |
font-family: 'Fira Code', monospace; | |
font-size: 0.9rem; | |
} | |
.markdown-content pre { | |
background: var(--background-tertiary); | |
padding: 1rem; | |
border-radius: 8px; | |
overflow-x: auto; | |
margin: 1rem 0; | |
} | |
/* === LOADING STATES === */ | |
.loading-spinner { | |
display: inline-block; | |
width: 20px; | |
height: 20px; | |
border: 3px solid rgba(10, 102, 194, 0.3); | |
border-radius: 50%; | |
border-top-color: var(--primary-color); | |
animation: spin 1s ease-in-out infinite; | |
} | |
@keyframes spin { | |
to { transform: rotate(360deg); } | |
} | |
/* === RESPONSIVE DESIGN === */ | |
@media (max-width: 768px) { | |
.main-header h1 { | |
font-size: 2rem !important; | |
} | |
.tabitem { | |
padding: 1rem; | |
} | |
.tab-nav button { | |
padding: 0.75rem 1rem !important; | |
font-size: 0.9rem !important; | |
} | |
} | |
/* === ANIMATIONS === */ | |
@keyframes fadeIn { | |
from { opacity: 0; transform: translateY(20px); } | |
to { opacity: 1; transform: translateY(0); } | |
} | |
.fade-in { | |
animation: fadeIn 0.6s ease-out; | |
} | |
/* === ACCESSIBILITY === */ | |
.sr-only { | |
position: absolute; | |
width: 1px; | |
height: 1px; | |
padding: 0; | |
margin: -1px; | |
overflow: hidden; | |
clip: rect(0, 0, 0, 0); | |
white-space: nowrap; | |
border: 0; | |
} | |
/* === GRADIO SPECIFIC OVERRIDES === */ | |
.gradio-container .wrap { | |
border-radius: var(--border-radius) !important; | |
border: 1px solid var(--border-color) !important; | |
} | |
.gradio-container .panel { | |
background: var(--background-primary) !important; | |
border-radius: var(--border-radius) !important; | |
} | |
.gradio-container .form { | |
background: transparent !important; | |
} | |
/* === STATUS INDICATORS === */ | |
.status-success { | |
color: var(--success-color) !important; | |
background: rgba(87, 196, 163, 0.1) !important; | |
padding: 0.5rem 1rem; | |
border-radius: 6px; | |
border-left: 4px solid var(--success-color); | |
} | |
.status-warning { | |
color: var(--warning-color) !important; | |
background: rgba(245, 184, 0, 0.1) !important; | |
padding: 0.5rem 1rem; | |
border-radius: 6px; | |
border-left: 4px solid var(--warning-color); | |
} | |
.status-error { | |
color: var(--error-color) !important; | |
background: rgba(204, 16, 22, 0.1) !important; | |
padding: 0.5rem 1rem; | |
border-radius: 6px; | |
border-left: 4px solid var(--error-color); | |
} | |
/* === HIDE GRADIO FOOTER === */ | |
footer { | |
display: none !important; | |
} | |
.footer { | |
display: none !important; | |
} | |
/* === IMPROVE SPACING === */ | |
.block { | |
margin-bottom: 1.5rem !important; | |
} | |
/* === CUSTOM COMPONENTS === */ | |
.metric-card { | |
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
color: white; | |
padding: 1.5rem; | |
border-radius: var(--border-radius); | |
text-align: center; | |
box-shadow: var(--shadow-medium); | |
margin: 0.5rem; | |
} | |
.metric-value { | |
font-size: 2rem; | |
font-weight: 700; | |
margin-bottom: 0.5rem; | |
} | |
.metric-label { | |
font-size: 0.9rem; | |
opacity: 0.9; | |
} | |
""" | |
# Custom theme | |
custom_theme = gr.themes.Soft( | |
primary_hue="blue", | |
secondary_hue="sky", | |
neutral_hue="slate", | |
spacing_size="md", | |
radius_size="md" | |
).set( | |
body_background_fill="linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)", | |
block_background_fill="white", | |
block_border_width="1px", | |
block_border_color="#e5e5e5", | |
block_radius="12px", | |
block_shadow="0 2px 8px rgba(0, 0, 0, 0.08)", | |
button_primary_background_fill="#0A66C2", | |
button_primary_background_fill_hover="#004182", | |
button_primary_text_color="white", | |
button_secondary_background_fill="#f8f9fa", | |
button_secondary_background_fill_hover="#f3f2ef", | |
input_border_color="#e5e5e5", | |
input_border_color_focus="#0A66C2", | |
input_border_width="2px" | |
) | |
def update_report_display_enhanced(selected_report_id: str, current_token_state: dict, format_report_for_display_func): | |
""" | |
Updates the report header and body display when a new report is selected. | |
This function now expects format_report_for_display to return a dict with | |
'header_html' and 'body_markdown'. | |
Args: | |
selected_report_id (str): The ID of the selected report. | |
current_token_state (dict): The current state dictionary containing data. | |
format_report_for_display_func (callable): The function to format report data. | |
Returns: | |
tuple: A tuple of gr.update objects for the header and body displays. | |
""" | |
# Define empty states for header and body with improved styling | |
empty_header_html = """ | |
<div class="card"> | |
<div class="card-header"> | |
π Comprehensive Analysis Report | |
</div> | |
<div class="card-content"> | |
<p>AI-Generated Insights from Your LinkedIn Data</p> | |
<div class="status-badge">Generated from Bubble.io</div> | |
</div> | |
</div> | |
""" | |
empty_body_markdown_no_selection = """ | |
<div class="card" style="text-align: center; padding: 3rem;"> | |
<div style="font-size: 3rem; margin-bottom: 1rem;">π</div> | |
<h3 style="color: var(--text-primary); margin-bottom: 0.5rem;">Select a Report</h3> | |
<p style="color: var(--text-secondary);"> | |
Choose a report from the dropdown above to view its detailed analysis and insights. | |
</p> | |
</div> | |
""" | |
empty_body_markdown_no_data = """ | |
<div class="card" style="text-align: center; padding: 3rem;"> | |
<div style="font-size: 3rem; margin-bottom: 1rem;">β οΈ</div> | |
<h3 style="color: var(--warning-color); margin-bottom: 0.5rem;">Data Not Available</h3> | |
<p style="color: var(--text-secondary);"> | |
Analysis data is not loaded or is empty. Please try refreshing the page. | |
</p> | |
</div> | |
""" | |
empty_body_markdown_not_found = lambda _id: f""" | |
<div class="card" style="text-align: center; padding: 3rem;"> | |
<div style="font-size: 3rem; margin-bottom: 1rem;">β</div> | |
<h3 style="color: var(--error-color); margin-bottom: 0.5rem;">Report Not Found</h3> | |
<p style="color: var(--text-secondary);"> | |
Report with ID '{_id}' was not found in the database. | |
</p> | |
</div> | |
""" | |
if not selected_report_id: | |
return gr.update(value=empty_header_html), gr.update(value=empty_body_markdown_no_selection) | |
agentic_df = current_token_state.get("bubble_agentic_analysis_data") | |
if agentic_df is None or agentic_df.empty: | |
return gr.update(value=empty_header_html), gr.update(value=empty_body_markdown_no_data) | |
selected_report_series_df = agentic_df[agentic_df['_id'] == selected_report_id] | |
if selected_report_series_df.empty: | |
return gr.update(value=empty_header_html), gr.update(value=empty_body_markdown_not_found(selected_report_id)) | |
selected_report_series = selected_report_series_df.iloc[0] | |
# Call the format_report_for_display, which now returns a dict | |
formatted_content_parts = format_report_for_display_func(selected_report_series) | |
# Update the two separate Gradio components | |
return ( | |
gr.update(value=formatted_content_parts['header_html']), | |
gr.update(value=formatted_content_parts['body_markdown']) | |
) | |
def build_main_app_ui( | |
PLOT_ID_TO_FORMULA_KEY_MAP: Dict[str, Any], | |
PLOT_FORMULAS: Dict[str, Any], | |
BOMB_ICON: str, | |
EXPLORE_ICON: str, | |
FORMULA_ICON: str, | |
ACTIVE_ICON: str, | |
build_analytics_tab_plot_area_func: Callable, | |
update_analytics_plots_figures_func: Callable, | |
create_placeholder_plot_func: Callable, | |
get_initial_insight_prompt_and_suggestions_func: Callable, | |
generate_llm_response_func: Callable, | |
build_home_tab_ui_func: Callable, | |
create_enhanced_report_tab_func: Callable, | |
create_enhanced_okr_tab_func: Callable, | |
format_report_for_display_func: Callable, # New argument to pass the formatting function | |
AGENTIC_MODULES_LOADED: bool, # ADD THIS PARAMETER | |
get_initial_okr_display_func: Callable = None, # ADD THIS PARAMETER TOO | |
# NEW: Added parameters for initial event binding functions | |
initial_data_load_sequence_func: Callable = None, | |
get_url_user_token_func: Callable = None, | |
load_and_display_agentic_results_func: Callable = None, | |
format_okrs_for_enhanced_display_func: Callable = None, | |
update_report_display_enhanced_func: Callable = None | |
) -> tuple: | |
""" | |
Builds the main Gradio application UI with enhanced styling and structure. | |
Args: | |
(All necessary functions and constants are passed as arguments to ensure self-containment | |
within the UI module without direct imports of non-UI related logic) | |
AGENTIC_MODULES_LOADED (bool): Whether agentic modules are loaded | |
get_initial_okr_display_func (callable): Function to get initial OKR display | |
initial_data_load_sequence_func (callable): Function to handle initial data load. | |
get_url_user_token_func (callable): Function to get URL user token. | |
load_and_display_agentic_results_func (callable): Function to load and display agentic results. | |
format_okrs_for_enhanced_display_func (callable): Function to format OKRs for enhanced display. | |
update_report_display_enhanced_func (callable): Function to update enhanced report display. | |
Returns: | |
tuple: A tuple containing: | |
- app (gr.Blocks): The main Gradio application object. | |
- url_user_token_display (gr.Textbox): Hidden component for user token. | |
- org_urn_display (gr.Textbox): Hidden component for organization URN. | |
- status_box (gr.Textbox): Component for system status. | |
- token_state (gr.State): Gradio state for token and data. | |
- reconstruction_cache_st (gr.State): State for reconstructed OKR data cache. | |
- enhanced_okr_display_html (gr.HTML): HTML component for enhanced OKR display. | |
- tabs (gr.Tabs): The main tabs component. | |
- report_selector_dd (gr.Dropdown): Dropdown for selecting reports. | |
- agentic_display_outputs (list): List of Gradio components for agentic results. | |
- analytics_tab_instance (AnalyticsTab): The analytics tab instance itself. | |
- chat_histories_st (gr.State): State for chat histories. | |
- current_chat_plot_id_st (gr.State): State for current chat plot ID. | |
- plot_data_for_chatbot_st (gr.State): State for plot data for chatbot. | |
- format_report_for_display_func (callable): The report display formatting function. | |
""" | |
app = gr.Blocks( | |
theme=custom_theme, | |
css=CUSTOM_CSS, | |
title="LinkedIn Organization Dashboard", | |
head=""" | |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap" rel="stylesheet"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
""" | |
) | |
with app: | |
# --- STATE MANAGEMENT --- | |
token_state = gr.State(value={ | |
"token": None, "client_id": None, "org_urn": None, | |
"bubble_posts_df": pd.DataFrame(), "bubble_post_stats_df": pd.DataFrame(), | |
"bubble_mentions_df": pd.DataFrame(), "bubble_follower_stats_df": pd.DataFrame(), | |
"bubble_agentic_analysis_data": pd.DataFrame(), # To store agentic results from Bubble | |
"url_user_token_temp_storage": None, | |
"config_date_col_posts": "published_at", "config_date_col_mentions": "date", | |
"config_date_col_followers": "date", "config_media_type_col": "media_type", | |
"config_eb_labels_col": "li_eb_label" | |
}) | |
# States for analytics tab chatbot | |
chat_histories_st = gr.State({}) | |
current_chat_plot_id_st = gr.State(None) | |
plot_data_for_chatbot_st = gr.State({}) | |
# States for agentic results display | |
orchestration_raw_results_st = gr.State(None) | |
# KEPT for compatibility with load_and_display_agentic_results signature | |
key_results_for_selection_st = gr.State([]) | |
selected_key_result_ids_st = gr.State([]) | |
# --- NEW: Session-specific cache for reconstructed OKR data --- | |
reconstruction_cache_st = gr.State({}) | |
# NEW: State to hold the actionable_okrs dictionary explicitly | |
actionable_okrs_data_st = gr.State({}) | |
# --- UI LAYOUT --- | |
# Main Header | |
with gr.Row(): | |
with gr.Column(): | |
gr.HTML(""" | |
<div class="main-header"> | |
<h1>π LinkedIn Organization Dashboard</h1> | |
<div class="subtitle">Advanced Analytics & AI-Powered Insights for LinkedIn Organizations</div> | |
</div> | |
""") | |
# Hidden components for token management | |
url_user_token_display = gr.Textbox(label="User Token (Hidden)", interactive=False, visible=False) | |
org_urn_display = gr.Textbox(label="Org URN (Hidden)", interactive=False, visible=False) | |
# Status section with better styling | |
with gr.Row(): | |
with gr.Column(): | |
gr.HTML('<div class="status-container">') | |
status_box = gr.Textbox( | |
label="π System Status", | |
interactive=False, | |
value="π Initializing dashboard...", | |
elem_classes=["status-display"] | |
) | |
gr.HTML('</div>') | |
# Instantiate AnalyticsTab here, after all its required gr.State components are defined | |
from services.analytics_tab_module import AnalyticsTab # Local import to ensure it's loaded within context | |
analytics_icons = {'bomb': BOMB_ICON, 'explore': EXPLORE_ICON, 'formula': FORMULA_ICON, 'active': ACTIVE_ICON} | |
analytics_tab_instance = AnalyticsTab( | |
token_state=token_state, | |
chat_histories_st=chat_histories_st, | |
current_chat_plot_id_st=current_chat_plot_id_st, | |
plot_data_for_chatbot_st=plot_data_for_chatbot_st, | |
plot_id_to_formula_map=PLOT_ID_TO_FORMULA_KEY_MAP, | |
plot_formulas_data=PLOT_FORMULAS, | |
icons=analytics_icons, | |
fn_build_plot_area=build_analytics_tab_plot_area_func, | |
fn_update_plot_figures=update_analytics_plots_figures_func, | |
fn_create_placeholder_plot=create_placeholder_plot_func, | |
fn_get_initial_insight=get_initial_insight_prompt_and_suggestions_func, | |
fn_generate_llm_response=generate_llm_response_func | |
) | |
with gr.Tabs(elem_classes=["tab-nav"]) as tabs: | |
# --- HOME TAB --- | |
with gr.TabItem("π Home", id="tab_home", elem_classes=["tabitem"]): | |
# Call the new function from ui_generators to build the Home tab content | |
btn_graphs, btn_reports, btn_okr, btn_help = build_home_tab_ui_func() | |
# Link buttons to tab selection | |
btn_graphs.click(fn=lambda: gr.update(selected="tab_analytics_module"), outputs=tabs) | |
btn_reports.click(fn=lambda: gr.update(selected="tab_agentic_report"), outputs=tabs) | |
btn_okr.click(fn=lambda: gr.update(selected="tab_agentic_okrs"), outputs=tabs) | |
# btn_help.click(fn=lambda: gr.update(selected="tab_help"), outputs=tabs) # Uncomment if you add a help tab | |
# Analytics Tab | |
analytics_tab_instance.create_tab_ui() # This is the "Graphs" tab, assuming its ID is "tab_analytics_module" | |
# --- AGENTIC ANALYSIS REPORT TAB --- | |
with gr.TabItem("π Analysis Reports", id="tab_agentic_report", visible=True, elem_classes=["tabitem"]): # Set visible to True to allow `create_enhanced_report_tab_func` to return components | |
# The create_enhanced_report_tab function handles the CSS and HTML structure | |
agentic_pipeline_status_md, report_selector_dd, report_header_html_display, report_body_markdown_display = \ | |
create_enhanced_report_tab_func(True) # Pass True to create elements | |
# --- AGENTIC OKRS TAB --- | |
with gr.TabItem("π― OKRs & Tasks", id="tab_agentic_okrs", visible=True, elem_classes=["tabitem"]): # Set visible to True to allow components to be created | |
gr.HTML(""" | |
<div class="card"> | |
<div class="card-header"> | |
π― AI Generated OKRs and Actionable Tasks | |
</div> | |
<div class="card-content"> | |
<p>Based on AI analysis, the agent has proposed the following OKRs and actionable tasks from Bubble.io data.</p> | |
</div> | |
</div> | |
""") | |
# The `AGENTIC_MODULES_LOADED` check should control visibility from app.py, not prevent creation here | |
# to avoid NoneType errors during UI building. | |
if not AGENTIC_MODULES_LOADED: # This check is for conditional display, not creation | |
gr.HTML(""" | |
<div class="card" style="text-align: center; padding: 2rem;"> | |
<div style="font-size: 2rem; margin-bottom: 1rem;">π΄</div> | |
<h3 style="color: var(--error-color);">Module Loading Error</h3> | |
<p>Agentic modules could not be loaded. This tab is currently unavailable.</p> | |
</div> | |
""") | |
# Create dummy components to maintain compatibility | |
enhanced_okr_display_html = gr.HTML(""" | |
<div class="card" style="text-align: center; padding: 2rem;"> | |
<div style="font-size: 2rem; margin-bottom: 1rem;">π΄</div> | |
<h3 style="color: var(--error-color);">OKR Display Unavailable</h3> | |
<p>Agentic modules are not loaded.</p> | |
</div> | |
""") | |
# Create hidden old components for compatibility | |
with gr.Column(visible=False): | |
gr.Markdown("### Suggested Key Results (OLD UI - HIDDEN)") | |
key_results_cbg = gr.CheckboxGroup(label="Select Key Results", choices=[], value=[], interactive=True) | |
gr.Markdown("### Detailed OKRs and Tasks (OLD UI - HIDDEN)") | |
okr_detail_display_md = gr.Markdown("I dettagli OKR appariranno qui.") | |
else: | |
# Keep the old components but make them invisible to maintain load_and_display_agentic_results signature | |
with gr.Column(visible=False): | |
gr.Markdown("### Suggested Key Results (OLD UI - HIDDEN)") | |
key_results_cbg = gr.CheckboxGroup(label="Select Key Results", choices=[], value=[], interactive=True) | |
gr.Markdown("### Detailed OKRs and Tasks (OLD UI - HIDDEN)") | |
okr_detail_display_md = gr.Markdown("I dettagli OKR appariranno qui.") | |
# NEW: Add the enhanced OKR display HTML component | |
enhanced_okr_display_html = create_enhanced_okr_tab_func() | |
# Add footer with enhanced styling | |
gr.HTML(""" | |
<div style="text-align: center; padding: 2rem; margin-top: 3rem; border-top: 1px solid var(--border-color); color: var(--text-tertiary);"> | |
<p>Β© 2024 LinkedIn Organization Dashboard - Powered by AI & Advanced Analytics</p> | |
<p style="font-size: 0.9rem; margin-top: 0.5rem;"> | |
π Built with Gradio β’ π LinkedIn API β’ π€ AI Analytics β’ βοΈ Bubble.io Integration | |
</p> | |
</div> | |
""") | |
# Ensure agentic_display_outputs correctly maps to the newly created components | |
# This list must match the outputs of load_and_display_agentic_results | |
# These variables are now defined within the `with app:` block | |
agentic_display_outputs = [ | |
agentic_pipeline_status_md, # 0: Status Markdown (hidden) | |
report_selector_dd, # 1: Dropdown for selecting reports | |
key_results_cbg if AGENTIC_MODULES_LOADED else gr.State([]), # Ensure it's a component or dummy state | |
okr_detail_display_md if AGENTIC_MODULES_LOADED else gr.State(None), # Ensure it's a component or dummy state | |
orchestration_raw_results_st, # 4: Raw results state | |
selected_key_result_ids_st, # 5: Selected KR IDs state (kept hidden) | |
key_results_for_selection_st, # 6: All KRs for selection state (kept hidden) | |
report_header_html_display, # 7: New HTML output for header | |
report_body_markdown_display, # 8: New Markdown output for body | |
reconstruction_cache_st, # 9: Reconstruction cache state | |
enhanced_okr_display_html, # 10: The enhanced HTML display for OKRs | |
actionable_okrs_data_st # 11: NEW: The actionable_okrs dictionary state | |
] | |
# Event handlers (moved here to be within the gr.Blocks context) | |
if get_url_user_token_func: # Check if the function is provided | |
app.load(fn=get_url_user_token_func, inputs=None, outputs=[url_user_token_display, org_urn_display], api_name="get_url_params", show_progress=False) | |
if AGENTIC_MODULES_LOADED and update_report_display_enhanced_func: # Check if function is provided | |
report_selector_dd.change( | |
fn=lambda sr_id, c_state: update_report_display_enhanced_func(sr_id, c_state, format_report_for_display_func), | |
inputs=[report_selector_dd, token_state], | |
outputs=[agentic_display_outputs[7], agentic_display_outputs[8]], # report_header_html_display, report_body_markdown_display | |
show_progress="minimal" | |
) | |
if initial_data_load_sequence_func: # Check if function is provided | |
initial_load_event = org_urn_display.change( | |
fn=initial_data_load_sequence_func, | |
inputs=[url_user_token_display, org_urn_display, token_state], | |
outputs=[status_box, token_state], | |
show_progress="full" | |
) | |
# Chain the loading events | |
if analytics_tab_instance: | |
initial_load_event.then( | |
fn=analytics_tab_instance._refresh_analytics_graphs_ui, | |
inputs=[token_state, analytics_tab_instance.date_filter_selector, analytics_tab_instance.custom_start_date_picker, | |
analytics_tab_instance.custom_end_date_picker, chat_histories_st], # Changed from chat_histories_st_returned | |
outputs=analytics_tab_instance.graph_refresh_outputs_list, | |
show_progress="full" | |
) | |
if AGENTIC_MODULES_LOADED and load_and_display_agentic_results_func: | |
initial_load_event.then( | |
fn=load_and_display_agentic_results_func, | |
inputs=[token_state, reconstruction_cache_st], | |
outputs=agentic_display_outputs, | |
show_progress="minimal" | |
) | |
if AGENTIC_MODULES_LOADED and format_okrs_for_enhanced_display_func: | |
initial_load_event.then( | |
fn=format_okrs_for_enhanced_display_func, | |
inputs=[reconstruction_cache_st], | |
outputs=[enhanced_okr_display_html], | |
show_progress="minimal" | |
) | |
return (app, url_user_token_display, org_urn_display, status_box, | |
token_state, reconstruction_cache_st, enhanced_okr_display_html, | |
tabs, report_selector_dd, agentic_display_outputs, | |
analytics_tab_instance, chat_histories_st, # Returned directly as defined in this function | |
current_chat_plot_id_st, plot_data_for_chatbot_st, # Returned directly as defined in this function | |
format_report_for_display_func) # Return format_report_for_display_func as well |