|
|
""" |
|
|
Enterprise UI Theme for CX AI Agent |
|
|
Professional styling and custom Gradio theme |
|
|
""" |
|
|
import gradio as gr |
|
|
|
|
|
|
|
|
def get_enterprise_theme(): |
|
|
""" |
|
|
Return enterprise-grade Gradio theme with professional styling |
|
|
""" |
|
|
return gr.themes.Soft( |
|
|
primary_hue="blue", |
|
|
secondary_hue="slate", |
|
|
neutral_hue="slate", |
|
|
font=("Inter", gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"), |
|
|
font_mono=("'IBM Plex Mono'", gr.themes.GoogleFont("IBM Plex Mono"), "monospace"), |
|
|
).set( |
|
|
|
|
|
button_primary_background_fill="*primary_600", |
|
|
button_primary_background_fill_hover="*primary_700", |
|
|
button_primary_text_color="white", |
|
|
button_secondary_background_fill="*neutral_100", |
|
|
button_secondary_background_fill_hover="*neutral_200", |
|
|
button_secondary_text_color="*neutral_800", |
|
|
|
|
|
|
|
|
input_background_fill="white", |
|
|
input_border_color="*neutral_300", |
|
|
input_shadow="0 1px 2px 0 rgba(0, 0, 0, 0.05)", |
|
|
|
|
|
|
|
|
block_background_fill="white", |
|
|
block_border_width="1px", |
|
|
block_border_color="*neutral_200", |
|
|
block_shadow="0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)", |
|
|
block_radius="0.75rem", |
|
|
block_padding="1.5rem", |
|
|
|
|
|
|
|
|
panel_background_fill="*neutral_50", |
|
|
panel_border_width="1px", |
|
|
panel_border_color="*neutral_200", |
|
|
) |
|
|
|
|
|
|
|
|
def get_custom_css(): |
|
|
""" |
|
|
Return custom CSS for enterprise styling |
|
|
""" |
|
|
return """ |
|
|
/* Enterprise theme customizations */ |
|
|
.gradio-container { |
|
|
max-width: 1600px !important; |
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important; |
|
|
} |
|
|
|
|
|
/* Header styling */ |
|
|
.app-header { |
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
|
padding: 2rem; |
|
|
color: white; |
|
|
border-radius: 0.75rem; |
|
|
margin-bottom: 2rem; |
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
|
|
|
.app-header h1 { |
|
|
margin: 0; |
|
|
font-size: 2rem; |
|
|
font-weight: 700; |
|
|
} |
|
|
|
|
|
.app-header p { |
|
|
margin: 0.5rem 0 0 0; |
|
|
opacity: 0.9; |
|
|
font-size: 1rem; |
|
|
} |
|
|
|
|
|
/* Navigation tabs */ |
|
|
.nav-tabs { |
|
|
display: flex; |
|
|
gap: 0.5rem; |
|
|
margin-bottom: 2rem; |
|
|
border-bottom: 2px solid #e5e7eb; |
|
|
padding-bottom: 0; |
|
|
} |
|
|
|
|
|
.nav-tab { |
|
|
padding: 0.75rem 1.5rem !important; |
|
|
border: none !important; |
|
|
border-bottom: 3px solid transparent !important; |
|
|
background: transparent !important; |
|
|
font-weight: 500 !important; |
|
|
color: #6b7280 !important; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s; |
|
|
} |
|
|
|
|
|
.nav-tab:hover { |
|
|
color: #374151 !important; |
|
|
background: #f3f4f6 !important; |
|
|
border-radius: 0.5rem 0.5rem 0 0 !important; |
|
|
} |
|
|
|
|
|
.nav-tab.active { |
|
|
color: #3b82f6 !important; |
|
|
border-bottom-color: #3b82f6 !important; |
|
|
background: #eff6ff !important; |
|
|
} |
|
|
|
|
|
/* Metric cards */ |
|
|
.metric-card { |
|
|
background: white; |
|
|
border: 1px solid #e5e7eb; |
|
|
border-radius: 0.75rem; |
|
|
padding: 1.5rem; |
|
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); |
|
|
transition: transform 0.2s, box-shadow 0.2s; |
|
|
} |
|
|
|
|
|
.metric-card:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
|
|
|
.metric-value { |
|
|
font-size: 2.25rem; |
|
|
font-weight: 700; |
|
|
color: #111827; |
|
|
margin: 0.5rem 0; |
|
|
} |
|
|
|
|
|
.metric-label { |
|
|
font-size: 0.875rem; |
|
|
font-weight: 500; |
|
|
color: #6b7280; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.05em; |
|
|
} |
|
|
|
|
|
.metric-change { |
|
|
font-size: 0.875rem; |
|
|
font-weight: 500; |
|
|
margin-top: 0.5rem; |
|
|
} |
|
|
|
|
|
.metric-change.positive { |
|
|
color: #10b981; |
|
|
} |
|
|
|
|
|
.metric-change.negative { |
|
|
color: #ef4444; |
|
|
} |
|
|
|
|
|
/* Status badges */ |
|
|
.status-badge { |
|
|
display: inline-block; |
|
|
padding: 0.25rem 0.75rem; |
|
|
border-radius: 9999px; |
|
|
font-size: 0.75rem; |
|
|
font-weight: 600; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.05em; |
|
|
} |
|
|
|
|
|
.status-active { |
|
|
background: #d1fae5; |
|
|
color: #065f46; |
|
|
} |
|
|
|
|
|
.status-draft { |
|
|
background: #e5e7eb; |
|
|
color: #374151; |
|
|
} |
|
|
|
|
|
.status-paused { |
|
|
background: #fef3c7; |
|
|
color: #92400e; |
|
|
} |
|
|
|
|
|
.status-completed { |
|
|
background: #dbeafe; |
|
|
color: #1e40af; |
|
|
} |
|
|
|
|
|
/* Data tables */ |
|
|
.data-table { |
|
|
width: 100%; |
|
|
border-collapse: collapse; |
|
|
background: white; |
|
|
border-radius: 0.75rem; |
|
|
overflow: hidden; |
|
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
|
|
|
.data-table thead { |
|
|
background: #f9fafb; |
|
|
border-bottom: 2px solid #e5e7eb; |
|
|
} |
|
|
|
|
|
.data-table th { |
|
|
padding: 0.75rem 1rem; |
|
|
text-align: left; |
|
|
font-size: 0.75rem; |
|
|
font-weight: 600; |
|
|
color: #6b7280; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.05em; |
|
|
} |
|
|
|
|
|
.data-table td { |
|
|
padding: 1rem; |
|
|
border-bottom: 1px solid #f3f4f6; |
|
|
color: #374151; |
|
|
} |
|
|
|
|
|
.data-table tr:hover { |
|
|
background: #f9fafb; |
|
|
} |
|
|
|
|
|
.data-table tr:last-child td { |
|
|
border-bottom: none; |
|
|
} |
|
|
|
|
|
/* Progress bars */ |
|
|
.progress-bar { |
|
|
width: 100%; |
|
|
height: 0.5rem; |
|
|
background: #e5e7eb; |
|
|
border-radius: 9999px; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.progress-fill { |
|
|
height: 100%; |
|
|
background: linear-gradient(90deg, #3b82f6 0%, #8b5cf6 100%); |
|
|
border-radius: 9999px; |
|
|
transition: width 0.3s ease; |
|
|
} |
|
|
|
|
|
/* Activity feed */ |
|
|
.activity-feed { |
|
|
background: white; |
|
|
border: 1px solid #e5e7eb; |
|
|
border-radius: 0.75rem; |
|
|
padding: 1.5rem; |
|
|
} |
|
|
|
|
|
.activity-item { |
|
|
display: flex; |
|
|
gap: 1rem; |
|
|
padding: 1rem 0; |
|
|
border-bottom: 1px solid #f3f4f6; |
|
|
} |
|
|
|
|
|
.activity-item:last-child { |
|
|
border-bottom: none; |
|
|
} |
|
|
|
|
|
.activity-icon { |
|
|
width: 2.5rem; |
|
|
height: 2.5rem; |
|
|
border-radius: 9999px; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
font-size: 1.25rem; |
|
|
flex-shrink: 0; |
|
|
} |
|
|
|
|
|
.activity-content { |
|
|
flex: 1; |
|
|
} |
|
|
|
|
|
.activity-title { |
|
|
font-weight: 500; |
|
|
color: #111827; |
|
|
margin-bottom: 0.25rem; |
|
|
} |
|
|
|
|
|
.activity-meta { |
|
|
font-size: 0.875rem; |
|
|
color: #6b7280; |
|
|
} |
|
|
|
|
|
/* Charts */ |
|
|
.chart-container { |
|
|
background: white; |
|
|
border: 1px solid #e5e7eb; |
|
|
border-radius: 0.75rem; |
|
|
padding: 1.5rem; |
|
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
|
|
|
.chart-title { |
|
|
font-size: 1.125rem; |
|
|
font-weight: 600; |
|
|
color: #111827; |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
|
|
|
/* Forms */ |
|
|
.form-group { |
|
|
margin-bottom: 1.5rem; |
|
|
} |
|
|
|
|
|
.form-label { |
|
|
display: block; |
|
|
font-size: 0.875rem; |
|
|
font-weight: 500; |
|
|
color: #374151; |
|
|
margin-bottom: 0.5rem; |
|
|
} |
|
|
|
|
|
.form-help { |
|
|
font-size: 0.75rem; |
|
|
color: #6b7280; |
|
|
margin-top: 0.25rem; |
|
|
} |
|
|
|
|
|
/* Empty states */ |
|
|
.empty-state { |
|
|
text-align: center; |
|
|
padding: 4rem 2rem; |
|
|
color: #6b7280; |
|
|
} |
|
|
|
|
|
.empty-state-icon { |
|
|
font-size: 3rem; |
|
|
margin-bottom: 1rem; |
|
|
opacity: 0.5; |
|
|
} |
|
|
|
|
|
.empty-state-title { |
|
|
font-size: 1.25rem; |
|
|
font-weight: 600; |
|
|
color: #374151; |
|
|
margin-bottom: 0.5rem; |
|
|
} |
|
|
|
|
|
.empty-state-description { |
|
|
font-size: 0.875rem; |
|
|
max-width: 28rem; |
|
|
margin: 0 auto 1.5rem; |
|
|
} |
|
|
|
|
|
/* Loading states */ |
|
|
.loading-spinner { |
|
|
display: inline-block; |
|
|
width: 1.5rem; |
|
|
height: 1.5rem; |
|
|
border: 3px solid #e5e7eb; |
|
|
border-top-color: #3b82f6; |
|
|
border-radius: 50%; |
|
|
animation: spin 0.6s linear infinite; |
|
|
} |
|
|
|
|
|
@keyframes spin { |
|
|
to { transform: rotate(360deg); } |
|
|
} |
|
|
|
|
|
/* Utility classes */ |
|
|
.text-center { text-align: center; } |
|
|
.text-right { text-align: right; } |
|
|
.text-sm { font-size: 0.875rem; } |
|
|
.text-xs { font-size: 0.75rem; } |
|
|
.font-semibold { font-weight: 600; } |
|
|
.font-bold { font-weight: 700; } |
|
|
.mb-2 { margin-bottom: 0.5rem; } |
|
|
.mb-4 { margin-bottom: 1rem; } |
|
|
.mt-2 { margin-top: 0.5rem; } |
|
|
.mt-4 { margin-top: 1rem; } |
|
|
.p-4 { padding: 1rem; } |
|
|
.flex { display: flex; } |
|
|
.gap-2 { gap: 0.5rem; } |
|
|
.gap-4 { gap: 1rem; } |
|
|
.items-center { align-items: center; } |
|
|
.justify-between { justify-content: space-between; } |
|
|
""" |
|
|
|
|
|
|
|
|
def create_header(): |
|
|
"""Create enterprise header component""" |
|
|
return gr.HTML(""" |
|
|
<div class="app-header"> |
|
|
<h1>🤖 CX AI Agent - Enterprise Edition</h1> |
|
|
<p>Autonomous Multi-Agent Customer Experience Platform powered by MCP</p> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
|
|
|
def create_metric_card(label: str, value: str, change: str = None, change_positive: bool = True): |
|
|
"""Create a metric card component""" |
|
|
change_class = "positive" if change_positive else "negative" |
|
|
change_arrow = "↑" if change_positive else "↓" |
|
|
change_html = f'<div class="metric-change {change_class}">{change_arrow} {change}</div>' if change else "" |
|
|
|
|
|
return f""" |
|
|
<div class="metric-card"> |
|
|
<div class="metric-label">{label}</div> |
|
|
<div class="metric-value">{value}</div> |
|
|
{change_html} |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
def create_status_badge(status: str): |
|
|
"""Create a status badge""" |
|
|
status_lower = status.lower() |
|
|
return f'<span class="status-badge status-{status_lower}">{status}</span>' |
|
|
|
|
|
|
|
|
def create_progress_bar(percentage: float): |
|
|
"""Create a progress bar""" |
|
|
return f""" |
|
|
<div class="progress-bar"> |
|
|
<div class="progress-fill" style="width: {percentage}%"></div> |
|
|
</div> |
|
|
""" |
|
|
|
|
|
|
|
|
def create_empty_state(icon: str, title: str, description: str, action_text: str = None): |
|
|
"""Create an empty state component""" |
|
|
action_html = f'<button class="btn btn-primary">{action_text}</button>' if action_text else "" |
|
|
|
|
|
return f""" |
|
|
<div class="empty-state"> |
|
|
<div class="empty-state-icon">{icon}</div> |
|
|
<div class="empty-state-title">{title}</div> |
|
|
<div class="empty-state-description">{description}</div> |
|
|
{action_html} |
|
|
</div> |
|
|
""" |
|
|
|