Spaces:
Running
Running
# ui_generators.py | |
""" | |
Generates HTML content and Matplotlib plots for the Gradio UI tabs, | |
and UI components for the Analytics tab. | |
""" | |
import pandas as pd | |
import logging | |
import matplotlib.pyplot as plt | |
import matplotlib # To ensure backend is switched before any plt import from other modules if app structure changes | |
import gradio as gr # Added for UI components | |
# Switch backend for Matplotlib to Agg for Gradio compatibility | |
matplotlib.use('Agg') | |
# Assuming config.py contains all necessary constants | |
from config import ( | |
BUBBLE_POST_DATE_COLUMN_NAME, BUBBLE_MENTIONS_DATE_COLUMN_NAME, BUBBLE_MENTIONS_ID_COLUMN_NAME, | |
FOLLOWER_STATS_TYPE_COLUMN, FOLLOWER_STATS_CATEGORY_COLUMN, FOLLOWER_STATS_ORGANIC_COLUMN, | |
FOLLOWER_STATS_PAID_COLUMN, FOLLOWER_STATS_CATEGORY_COLUMN_DT, UI_DATE_FORMAT, UI_MONTH_FORMAT | |
) | |
# Configure logging for this module if not already configured at app level | |
# logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(module)s - %(module)s - %(message)s') | |
# --- Constants for Button Icons/Text --- | |
# These are also defined/imported in app.py, ensure consistency | |
BOMB_ICON = "π£" | |
EXPLORE_ICON = "π§" | |
FORMULA_ICON = "Ζ" | |
ACTIVE_ICON = "β Close" # Ensure this matches app.py | |
def build_home_tab_ui(): | |
""" | |
Constructs the entire UI for the Home tab, including the header, | |
overview, feature cards, and navigation buttons. | |
Returns: | |
tuple: A tuple containing the Gradio Button components for | |
Graphs, Reports, and OKR Table, allowing app.py to | |
attach click handlers for tab navigation. | |
""" | |
# Custom CSS for enhanced styling | |
css_styles = """ | |
<style> | |
.hero-section { | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
padding: 60px 40px; | |
border-radius: 24px; | |
margin-bottom: 40px; | |
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.25); | |
color: white; | |
text-align: center; | |
position: relative; | |
overflow: hidden; | |
} | |
.hero-section::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="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.05)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.05)"/><circle cx="50" cy="10" r="0.5" fill="rgba(255,255,255,0.03)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>'); | |
opacity: 0.1; | |
} | |
.hero-content { | |
position: relative; | |
z-index: 1; | |
} | |
.hero-title { | |
font-size: 3.2em; | |
font-weight: 800; | |
margin-bottom: 24px; | |
background: linear-gradient(45deg, #ffffff, #e0e7ff); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
background-clip: text; | |
text-shadow: 2px 2px 4px rgba(0,0,0,0.3); | |
line-height: 1.2; | |
} | |
.hero-subtitle { | |
font-size: 1.4em; | |
line-height: 1.6; | |
margin-bottom: 16px; | |
opacity: 0.95; | |
font-weight: 300; | |
} | |
.hero-description { | |
font-size: 1.1em; | |
opacity: 0.85; | |
font-weight: 300; | |
max-width: 600px; | |
margin: 0 auto; | |
} | |
.overview-section { | |
background: #ffffff; | |
padding: 40px; | |
border-radius: 20px; | |
margin-bottom: 40px; | |
box-shadow: 0 10px 40px rgba(0,0,0,0.08); | |
border: 1px solid rgba(0,0,0,0.05); | |
} | |
.overview-header { | |
color: #1a202c; | |
margin-bottom: 24px; | |
font-size: 2.2em; | |
font-weight: 700; | |
display: flex; | |
align-items: center; | |
gap: 16px; | |
} | |
.overview-text { | |
font-size: 1.15em; | |
color: #4a5568; | |
line-height: 1.7; | |
margin-bottom: 32px; | |
font-weight: 400; | |
} | |
.features-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); | |
gap: 24px; | |
margin-top: 32px; | |
} | |
.feature-item { | |
display: flex; | |
align-items: center; | |
padding: 20px; | |
background: linear-gradient(135deg, #f8fafc, #f1f5f9); | |
border-radius: 16px; | |
border: 1px solid rgba(0,0,0,0.06); | |
transition: all 0.3s ease; | |
} | |
.feature-item:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 8px 25px rgba(0,0,0,0.1); | |
} | |
.feature-icon { | |
font-size: 2em; | |
margin-right: 16px; | |
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1)); | |
} | |
.feature-text { | |
color: #2d3748; | |
font-weight: 500; | |
font-size: 1.05em; | |
} | |
.nav-cards-container { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); | |
gap: 30px; | |
margin-bottom: 40px; | |
} | |
.nav-card { | |
padding: 36px; | |
border-radius: 20px; | |
min-height: 280px; | |
display: flex; | |
flex-direction: column; | |
justify-content: space-between; | |
box-shadow: 0 15px 45px rgba(0,0,0,0.12); | |
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
position: relative; | |
overflow: hidden; | |
} | |
.nav-card::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%); | |
opacity: 0; | |
transition: opacity 0.3s ease; | |
} | |
.nav-card:hover::before { | |
opacity: 1; | |
} | |
.nav-card:hover { | |
transform: translateY(-8px) scale(1.02); | |
box-shadow: 0 25px 60px rgba(0,0,0,0.15); | |
} | |
.nav-card-graphs { | |
background: linear-gradient(135deg, #4CAF50, #2E7D32); | |
} | |
.nav-card-reports { | |
background: linear-gradient(135deg, #2196F3, #1565C0); | |
} | |
.nav-card-okr { | |
background: linear-gradient(135deg, #FF9800, #E65100); | |
} | |
.nav-card-help { | |
background: linear-gradient(135deg, #9C27B0, #4A148C); | |
} | |
.nav-card-header { | |
color: white; | |
margin-bottom: 20px; | |
font-size: 1.5em; | |
font-weight: 700; | |
display: flex; | |
align-items: center; | |
gap: 16px; | |
} | |
.nav-card-icon { | |
font-size: 2.2em; | |
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2)); | |
} | |
.nav-card-description { | |
color: rgba(255,255,255,0.95); | |
line-height: 1.7; | |
font-size: 1.08em; | |
margin-bottom: 24px; | |
font-weight: 300; | |
} | |
.nav-card-footer { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-top: 20px; | |
position: relative; | |
z-index: 1; | |
} | |
.nav-card-features { | |
color: rgba(255,255,255,0.85); | |
font-size: 0.95em; | |
line-height: 1.6; | |
} | |
.nav-card-badge { | |
background: rgba(255,255,255,0.2); | |
padding: 12px; | |
border-radius: 12px; | |
backdrop-filter: blur(10px); | |
} | |
.nav-card-badge-icon { | |
font-size: 2.2em; | |
} | |
.how-it-works-section { | |
background: linear-gradient(135deg, #f8fafc, #e2e8f0); | |
padding: 40px; | |
border-radius: 20px; | |
margin-top: 40px; | |
border: 1px solid rgba(0,0,0,0.06); | |
} | |
.how-it-works-header { | |
color: #1a202c; | |
margin-bottom: 32px; | |
font-size: 2em; | |
font-weight: 700; | |
display: flex; | |
align-items: center; | |
gap: 16px; | |
} | |
.steps-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); | |
gap: 32px; | |
} | |
.step-item { | |
display: flex; | |
align-items: flex-start; | |
gap: 20px; | |
} | |
.step-number { | |
background: linear-gradient(135deg, #667eea, #764ba2); | |
color: white; | |
border-radius: 50%; | |
width: 48px; | |
height: 48px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
flex-shrink: 0; | |
font-weight: 700; | |
font-size: 1.2em; | |
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3); | |
} | |
.step-content { | |
flex: 1; | |
} | |
.step-title { | |
color: #1a202c; | |
font-weight: 700; | |
font-size: 1.2em; | |
margin-bottom: 8px; | |
} | |
.step-description { | |
color: #4a5568; | |
font-size: 1em; | |
line-height: 1.6; | |
} | |
.button-container { | |
margin-top: 24px; | |
} | |
@media (max-width: 768px) { | |
.hero-title { | |
font-size: 2.4em; | |
} | |
.nav-cards-container { | |
grid-template-columns: 1fr; | |
} | |
.nav-card { | |
min-height: 240px; | |
padding: 28px; | |
} | |
.hero-section { | |
padding: 40px 24px; | |
} | |
.overview-section, .how-it-works-section { | |
padding: 28px; | |
} | |
} | |
</style> | |
""" | |
with gr.Column(scale=1, elem_classes="home-page-container"): | |
# Inject custom CSS | |
gr.HTML(css_styles) | |
# Main hero section | |
gr.HTML(""" | |
<div class="hero-section"> | |
<div class="hero-content"> | |
<h1 class="hero-title"> | |
π LinkedIn Employer Brand Analytics | |
</h1> | |
<p class="hero-subtitle"> | |
Transform your LinkedIn presence with data-driven insights and actionable strategies | |
</p> | |
<p class="hero-description"> | |
Measure, analyze, and enhance your employer brand to attract top talent and build a stronger digital presence | |
</p> | |
</div> | |
</div> | |
""") | |
# Overview section with improved spacing | |
gr.HTML(""" | |
<div class="overview-section"> | |
<h2 class="overview-header"> | |
<span>π</span> What This Dashboard Offers | |
</h2> | |
<p class="overview-text"> | |
Our comprehensive analytics platform helps you understand and optimize your LinkedIn employer brand performance with real-time insights, automated reporting, and AI-powered recommendations. | |
</p> | |
<div class="features-grid"> | |
<div class="feature-item"> | |
<span class="feature-icon">π</span> | |
<span class="feature-text">Real-time data visualization and trend analysis</span> | |
</div> | |
<div class="feature-item"> | |
<span class="feature-icon">π</span> | |
<span class="feature-text">Automated quarterly and weekly performance reports</span> | |
</div> | |
<div class="feature-item"> | |
<span class="feature-icon">π―</span> | |
<span class="feature-text">AI-powered OKRs and actionable recommendations</span> | |
</div> | |
<div class="feature-item"> | |
<span class="feature-icon">π</span> | |
<span class="feature-text">Strategic insights to improve employer branding</span> | |
</div> | |
</div> | |
</div> | |
""") | |
# Navigation cards with improved layout | |
gr.HTML(""" | |
<div class="nav-cards-container"> | |
<div class="nav-card nav-card-graphs"> | |
<div> | |
<h3 class="nav-card-header"> | |
<span class="nav-card-icon">π</span> Interactive Graphs | |
</h3> | |
<p class="nav-card-description"> | |
Explore dynamic visualizations of your LinkedIn performance metrics. Track post engagement, | |
follower growth, mentions sentiment, and identify trends over time with interactive charts | |
and advanced filtering options. | |
</p> | |
</div> | |
<div class="nav-card-footer"> | |
<div class="nav-card-features"> | |
β¨ Real-time analytics<br/> | |
π Multiple chart types<br/> | |
π Advanced filtering | |
</div> | |
<div class="nav-card-badge"> | |
<span class="nav-card-badge-icon">π</span> | |
</div> | |
</div> | |
</div> | |
<div class="nav-card nav-card-reports"> | |
<div> | |
<h3 class="nav-card-header"> | |
<span class="nav-card-icon">π</span> Analysis Reports | |
</h3> | |
<p class="nav-card-description"> | |
Access comprehensive quarterly and weekly reports powered by AI analysis. Get detailed | |
insights into your employer brand performance, competitor analysis, and market positioning | |
with automated report generation. | |
</p> | |
</div> | |
<div class="nav-card-footer"> | |
<div class="nav-card-features"> | |
π Automated reports<br/> | |
π€ AI-powered insights<br/> | |
π Weekly & quarterly | |
</div> | |
<div class="nav-card-badge"> | |
<span class="nav-card-badge-icon">π</span> | |
</div> | |
</div> | |
</div> | |
<div class="nav-card nav-card-okr"> | |
<div> | |
<h3 class="nav-card-header"> | |
<span class="nav-card-icon">π―</span> OKR Action Plan | |
</h3> | |
<p class="nav-card-description"> | |
Discover AI-generated Objectives and Key Results (OKRs) with concrete action items. | |
Transform data insights into measurable goals and strategic initiatives to enhance | |
your employer brand effectively. | |
</p> | |
</div> | |
<div class="nav-card-footer"> | |
<div class="nav-card-features"> | |
π― Strategic objectives<br/> | |
β Actionable tasks<br/> | |
π Measurable outcomes | |
</div> | |
<div class="nav-card-badge"> | |
<span class="nav-card-badge-icon">π―</span> | |
</div> | |
</div> | |
</div> | |
<div class="nav-card nav-card-help"> | |
<div> | |
<h3 class="nav-card-header"> | |
<span class="nav-card-icon">π‘</span> Getting Started | |
</h3> | |
<p class="nav-card-description"> | |
New to employer brand analytics? Start with the Graphs section to | |
understand your current performance, then check Reports for detailed | |
analysis, and finally explore OKRs for actionable next steps. | |
</p> | |
</div> | |
<div class="nav-card-footer"> | |
<div style="background: rgba(255,255,255,0.15); padding: 16px; border-radius: 12px; width: 100%; text-align: center;"> | |
<div style="color: rgba(255,255,255,0.95); font-size: 1em;"> | |
<strong>πͺ Pro Tip:</strong><br/> | |
<span style="font-size: 0.9em;">Regular monitoring leads to 40% better employer brand performance</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
""") | |
# Create buttons with better spacing | |
with gr.Row(equal_height=True): | |
with gr.Column(): | |
btn_graphs = gr.Button("π Explore Graphs", variant="primary", size="lg", | |
elem_classes="nav-button", scale=1) | |
with gr.Column(): | |
btn_reports = gr.Button("π View Reports", variant="primary", size="lg", | |
elem_classes="nav-button", scale=1) | |
with gr.Row(equal_height=True): | |
with gr.Column(): | |
btn_okr = gr.Button("π― Access OKRs", variant="primary", size="lg", | |
elem_classes="nav-button", scale=1) | |
with gr.Column(): | |
btn_help = gr.Button("π Documentation", variant="secondary", size="lg", | |
elem_classes="nav-button", scale=1) | |
# How it works section with enhanced styling | |
gr.HTML(""" | |
<div class="how-it-works-section"> | |
<h3 class="how-it-works-header"> | |
<span>βΉοΈ</span> How It Works | |
</h3> | |
<div class="steps-grid"> | |
<div class="step-item"> | |
<div class="step-number">1</div> | |
<div class="step-content"> | |
<div class="step-title">Data Collection</div> | |
<div class="step-description">Automatically syncs with your LinkedIn organization data and processes engagement metrics in real-time</div> | |
</div> | |
</div> | |
<div class="step-item"> | |
<div class="step-number">2</div> | |
<div class="step-content"> | |
<div class="step-title">AI Analysis</div> | |
<div class="step-description">Advanced algorithms analyze trends, sentiment, and performance patterns to generate actionable insights</div> | |
</div> | |
</div> | |
<div class="step-item"> | |
<div class="step-number">3</div> | |
<div class="step-content"> | |
<div class="step-title">Actionable Results</div> | |
<div class="step-description">Receive specific recommendations, measurable goals, and strategic action plans to improve your employer brand</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
""") | |
return btn_graphs, btn_reports, btn_okr, btn_help | |
def create_analytics_plot_panel(plot_label_str, plot_id_str): | |
""" | |
Creates an individual plot panel with its plot component and action buttons. | |
Plot title and action buttons are on the same row. | |
Returns the panel (Column), plot component, and button components. | |
""" | |
# Icons are defined globally or imported. For this function, ensure they are accessible. | |
# If not using from config directly here, you might need to pass them or use fixed strings. | |
# Using fixed strings as a fallback if import fails, though they should be available via app.py's import. | |
local_bomb_icon, local_explore_icon, local_formula_icon = BOMB_ICON, EXPLORE_ICON, FORMULA_ICON | |
with gr.Column(visible=True) as panel_component: # Main container for this plot | |
with gr.Row(variant="compact"): | |
gr.Markdown(f"#### {plot_label_str}") # Plot title (scale might help balance) | |
with gr.Row(elem_classes="plot-actions", scale=1): # Action buttons container, give it some min_width | |
bomb_button = gr.Button(value=local_bomb_icon, variant="secondary", size="sm", min_width=30, elem_id=f"bomb_btn_{plot_id_str}") | |
formula_button = gr.Button(value=local_formula_icon, variant="secondary", size="sm", min_width=30, elem_id=f"formula_btn_{plot_id_str}") | |
explore_button = gr.Button(value=local_explore_icon, variant="secondary", size="sm", min_width=30, elem_id=f"explore_btn_{plot_id_str}") | |
# MODIFIED: Added height to gr.Plot for consistent sizing | |
plot_component = gr.Plot(label=plot_label_str, show_label=False) # Adjust height as needed | |
logging.debug(f"Created analytics panel for: {plot_label_str} (ID: {plot_id_str}) with fixed plot height.") | |
return panel_component, plot_component, bomb_button, explore_button, formula_button | |
def build_analytics_tab_plot_area(plot_configs): | |
""" | |
Builds the main plot area for the Analytics tab, arranging plot panels into rows of two, | |
with section titles appearing before their respective plots. | |
Returns a tuple: | |
- plot_ui_objects (dict): Dictionary of plot UI objects. | |
- section_titles_map (dict): Dictionary mapping section names to their gr.Markdown title components. | |
""" | |
logging.info(f"Building plot area for {len(plot_configs)} analytics plots with interleaved section titles.") | |
plot_ui_objects = {} | |
section_titles_map = {} | |
last_rendered_section = None | |
idx = 0 | |
while idx < len(plot_configs): | |
current_plot_config = plot_configs[idx] | |
current_section_name = current_plot_config["section"] | |
# Render section title if it's new for this block of plots | |
if current_section_name != last_rendered_section: | |
if current_section_name not in section_titles_map: | |
# Create the Markdown component for the section title | |
section_md_component = gr.Markdown(f"## {current_section_name}", visible=True) | |
section_titles_map[current_section_name] = section_md_component | |
logging.debug(f"Rendered and stored Markdown for section: {current_section_name}") | |
# No 'else' needed here for visibility, as it's handled by click handlers if sections are hidden/shown. | |
# The component is created once and its visibility is controlled elsewhere. | |
last_rendered_section = current_section_name | |
with gr.Row(equal_height=True): # Row for one or two plots. equal_height=False allows plots to define their height. | |
# --- Process the first plot in the row (config1) --- | |
config1 = plot_configs[idx] | |
# Safety check for section consistency (should always pass if configs are ordered by section) | |
if config1["section"] != current_section_name: | |
logging.warning(f"Plot {config1['id']} section mismatch. Expected {current_section_name}, got {config1['section']}. This might affect layout if a new section title was expected.") | |
# If a new section starts unexpectedly, ensure its title is created if missing | |
if config1["section"] not in section_titles_map: | |
sec_md = gr.Markdown(f"### {config1['section']}", visible=True) # Create and make visible | |
section_titles_map[config1['section']] = sec_md | |
last_rendered_section = config1["section"] # Update the current section context | |
panel_col1, plot_comp1, bomb_btn1, explore_btn1, formula_btn1 = \ | |
create_analytics_plot_panel(config1["label"], config1["id"]) | |
plot_ui_objects[config1["id"]] = { | |
"plot_component": plot_comp1, "bomb_button": bomb_btn1, | |
"explore_button": explore_btn1, "formula_button": formula_btn1, | |
"label": config1["label"], "panel_component": panel_col1, # This is the gr.Column containing the plot and its actions | |
"section": config1["section"] | |
} | |
logging.debug(f"Created UI panel for plot_id: {config1['id']} in section {config1['section']}") | |
idx += 1 | |
# --- Process the second plot in the row (config2), if applicable --- | |
if idx < len(plot_configs): | |
config2 = plot_configs[idx] | |
# Only add to the same row if it's part of the same section | |
if config2["section"] == current_section_name: | |
panel_col2, plot_comp2, bomb_btn2, explore_btn2, formula_btn2 = \ | |
create_analytics_plot_panel(config2["label"], config2["id"]) | |
plot_ui_objects[config2["id"]] = { | |
"plot_component": plot_comp2, "bomb_button": bomb_btn2, | |
"explore_button": explore_btn2, "formula_button": formula_btn2, | |
"label": config2["label"], "panel_component": panel_col2, | |
"section": config2["section"] | |
} | |
logging.debug(f"Created UI panel for plot_id: {config2['id']} in same row, section {config2['section']}") | |
idx += 1 | |
# If the next plot is in a new section, it will be handled in the next iteration of the while loop, | |
# starting with a new section title and a new gr.Row. | |
logging.info(f"Finished building plot area. Total plot objects: {len(plot_ui_objects)}. Section titles created: {len(section_titles_map)}") | |
if len(plot_ui_objects) != len(plot_configs): | |
logging.error(f"MISMATCH: Expected {len(plot_configs)} plot objects, but created {len(plot_ui_objects)}.") | |
return plot_ui_objects, section_titles_map | |
def create_enhanced_report_tab(agentic_modules_loaded_status: bool): | |
""" | |
Creates an enhanced report tab with Medium-style design for optimal readability. | |
This function returns the Gradio components that will be updated. | |
""" | |
# Custom CSS for Medium-style design | |
report_css = """ | |
<style> | |
/* Medium-style Report Container */ | |
.report-container { | |
max-width: 800px; | |
margin: 0 auto; | |
padding: 40px 20px; | |
font-family: 'Georgia', 'Charter', 'Times New Roman', serif; | |
line-height: 1.6; | |
color: #292929; | |
background: #ffffff; | |
} | |
/* Header Section */ | |
.report-header { | |
text-align: center; | |
margin-bottom: 48px; | |
padding-bottom: 24px; | |
border-bottom: 1px solid #e6e6e6; | |
} | |
.report-title { | |
font-size: 2.5rem; | |
font-weight: 700; | |
color: #1a1a1a; | |
margin-bottom: 16px; | |
letter-spacing: -0.02em; | |
} | |
.report-subtitle { | |
font-size: 1.2rem; | |
color: #6b6b6b; | |
font-weight: 400; | |
margin-bottom: 24px; | |
} | |
/* Status Badge */ | |
.status-badge { | |
display: inline-flex; | |
align-items: center; | |
padding: 8px 16px; | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
color: white; | |
border-radius: 20px; | |
font-size: 0.9rem; | |
font-weight: 500; | |
margin-bottom: 16px; | |
} | |
.status-badge::before { | |
content: "π"; | |
margin-right: 8px; | |
} | |
/* Report Selection Area */ | |
.report-selection { | |
background: #f8f9fa; | |
padding: 32px; | |
border-radius: 12px; | |
margin-bottom: 40px; | |
border: 1px solid #e9ecef; | |
} | |
.selection-title { | |
font-size: 1.3rem; | |
font-weight: 600; | |
color: #1a1a1a; | |
margin-bottom: 16px; | |
display: flex; | |
align-items: center; | |
} | |
.selection-title::before { | |
content: "π"; | |
margin-right: 12px; | |
font-size: 1.5rem; | |
} | |
.selection-description { | |
color: #6b6b6b; | |
margin-bottom: 24px; | |
font-size: 1.05rem; | |
} | |
/* Report Content Area */ | |
.report-content { | |
background: white; | |
padding: 0; | |
border-radius: 8px; | |
box-shadow: 0 2px 12px rgba(0,0,0,0.04); | |
border: 1px solid #e9ecef; | |
min-height: 400px; | |
} | |
/* MODIFIED: Adjusted target for the report body's content */ | |
.report-body-content { /* New class for the Markdown content container */ | |
padding: 40px; | |
font-size: 1.1rem; | |
line-height: 1.8; | |
} | |
/* Typography Enhancements for the Markdown content */ | |
.report-body-content h1 { | |
font-size: 2.2rem; | |
font-weight: 700; | |
color: #1a1a1a; | |
margin-bottom: 24px; | |
margin-top: 0; | |
letter-spacing: -0.02em; | |
} | |
.report-body-content h2 { | |
font-size: 1.8rem; | |
font-weight: 600; | |
color: #1a1a1a; | |
margin-top: 48px; | |
margin-bottom: 20px; | |
padding-bottom: 12px; | |
border-bottom: 2px solid #e6e6e6; | |
} | |
.report-body-content h3 { | |
font-size: 1.4rem; | |
font-weight: 600; | |
color: #2c2c2c; | |
margin-top: 36px; | |
margin-bottom: 16px; | |
} | |
.report-body-content p { | |
margin-bottom: 20px; | |
color: #292929; | |
font-size: 1.1rem; | |
} | |
.report-body-content ul, .report-body-content ol { | |
margin-bottom: 20px; | |
padding-left: 24px; | |
} | |
.report-body-content li { | |
margin-bottom: 8px; | |
color: #292929; | |
} | |
.report-body-content strong { | |
font-weight: 600; | |
color: #1a1a1a; | |
} | |
.report-body-content em { | |
font-style: italic; | |
color: #4a4a4a; | |
} | |
/* Quote Styling */ | |
.report-body-content blockquote { | |
border-left: 4px solid #667eea; | |
padding-left: 20px; | |
margin: 24px 0; | |
font-style: italic; | |
color: #4a4a4a; | |
background: #f8f9fa; | |
padding: 20px; | |
border-radius: 4px; | |
} | |
/* Code Styling */ | |
.report-body-content code { | |
background: #f1f3f4; | |
padding: 2px 6px; | |
border-radius: 4px; | |
font-family: 'Monaco', 'Menlo', monospace; | |
font-size: 0.9em; | |
color: #d73a49; | |
} | |
/* Empty State */ | |
.empty-state { | |
text-align: center; | |
padding: 80px 40px; | |
color: #6b6b6b; | |
} | |
.empty-state-icon { | |
font-size: 4rem; | |
margin-bottom: 24px; | |
opacity: 0.6; | |
} | |
.empty-state-title { | |
font-size: 1.5rem; | |
font-weight: 600; | |
margin-bottom: 12px; | |
color: #4a4a4a; | |
} | |
.empty-state-description { | |
font-size: 1.1rem; | |
line-height: 1.6; | |
max-width: 400px; | |
margin: 0 auto; | |
} | |
/* Responsive Design */ | |
@media (max-width: 768px) { | |
.report-container { | |
padding: 24px 16px; | |
} | |
.report-title { | |
font-size: 2rem; | |
} | |
.report-selection { | |
padding: 24px; | |
} | |
.report-body-content { /* Target the inner HTML component directly */ | |
padding: 24px; | |
} | |
.report-body-content h1 { | |
font-size: 1.8rem; | |
} | |
.report-body-content h2 { | |
font-size: 1.5rem; | |
} | |
.empty-state { | |
padding: 60px 20px; | |
} | |
} | |
/* Gradio Component Overrides */ | |
.report-content .gradio-container { | |
background: transparent !important; | |
border: none !important; | |
} | |
/* Dropdown Styling */ | |
.report-selection .gr-dropdown { | |
background: white; | |
border: 2px solid #e9ecef; | |
border-radius: 8px; | |
font-size: 1.05rem; | |
} | |
.report-selection .gr-dropdown:focus { | |
border-color: #667eea; | |
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); | |
} | |
/* Loading Indicator: Hidden by default */ | |
.loading-indicator { | |
display: none !important; /* Hide the entire loading indicator block */ | |
} | |
</style> | |
""" | |
# Inject custom CSS | |
gr.HTML(report_css) | |
# Main container | |
with gr.Column(elem_classes=["report-container"]): | |
# Header section | |
with gr.Column(elem_classes=["report-header"]): | |
# This HTML component will display the dynamic title and subtitle | |
report_header_html_display = gr.HTML(""" | |
<div class="report-title">π Comprehensive Analysis Report</div> | |
<div class="report-subtitle">AI-Generated Insights from Your LinkedIn Data</div> | |
<div class="status-badge">Generated from Bubble.io</div> | |
""") | |
# Report selection section | |
with gr.Column(elem_classes=["report-selection"]): | |
gr.HTML(""" | |
<div class="selection-title">Report Library</div> | |
<div class="selection-description"> | |
Select a pre-generated report from your analysis library to view detailed insights, | |
trends, and recommendations based on your LinkedIn organization data. | |
</div> | |
""") | |
# Status indicator (now hidden by default via CSS) | |
agentic_pipeline_status_md = gr.Markdown( | |
"π **Status:** Loading report data...", | |
elem_classes=["loading-indicator"] # This class now hides the element | |
) | |
# Report selector dropdown | |
with gr.Row(): | |
report_selector_dd = gr.Dropdown( | |
label="π Select Report", | |
choices=[], | |
interactive=True, | |
info="Choose from your available analysis reports", | |
elem_classes=["report-dropdown"] | |
) | |
# Report content display area | |
with gr.Column(elem_classes=["report-content"]): | |
# This Markdown component will display the actual report text, formatted as Markdown | |
report_body_markdown_display = gr.Markdown( | |
""" | |
<div class="empty-state"> | |
<div class="empty-state-icon">π</div> | |
<div class="empty-state-title">No Report Selected</div> | |
<div class="empty-state-description"> | |
Please select a report from the library above to view its detailed analysis and insights. | |
</div> | |
</div> | |
""", | |
# Apply styles to the content within the Markdown component using a wrapper div | |
elem_classes=["report-body-content"] | |
) | |
# Error handling for when modules are not loaded | |
if not agentic_modules_loaded_status: | |
gr.HTML(""" | |
<div style=" | |
background: #fee; | |
border: 1px solid #fcc; | |
border-radius: 8px; | |
padding: 20px; | |
margin-top: 20px; | |
text-align: center; | |
"> | |
<div style="color: #c33; font-size: 1.2rem; font-weight: 600; margin-bottom: 8px;"> | |
β οΈ Module Loading Error | |
</div> | |
<div style="color: #666;"> | |
Agentic analysis modules could not be loaded. Please check your configuration. | |
</div> | |
</div> | |
""") | |
# Return both header HTML and body Markdown components for app.py to update | |
return agentic_pipeline_status_md, report_selector_dd, report_header_html_display, report_body_markdown_display | |