feat: redesign dashboard with modern UI

Replace sidebar navigation with horizontal tabs and add comprehensive
CSS styling with dark theme, glassmorphism cards, gradient accents,
and improved visual hierarchy. Updates all page components with
consistent modern design language.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-03-13 15:36:56 -04:00
parent d371ceeec8
commit 7eb72ab549
+507 -91
View File
@@ -17,11 +17,304 @@ from SPARC import config
st.set_page_config( st.set_page_config(
page_title="SPARC Dashboard", page_title="SPARC Dashboard",
page_icon="📊", page_icon="",
layout="wide", layout="wide",
initial_sidebar_state="expanded", initial_sidebar_state="collapsed",
) )
# Modern CSS styling
st.markdown("""
<style>
/* Hide default Streamlit elements */
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
header {visibility: hidden;}
/* Root variables for theming */
:root {
--primary: #6366f1;
--primary-dark: #4f46e5;
--secondary: #0ea5e9;
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
--bg-dark: #0f172a;
--bg-card: #1e293b;
--bg-card-hover: #334155;
--text-primary: #f8fafc;
--text-secondary: #94a3b8;
--border: #334155;
}
/* Main app background */
.stApp {
background: linear-gradient(135deg, #0f172a 0%, #1e1b4b 100%);
}
/* Top navigation bar */
.nav-container {
background: rgba(30, 41, 59, 0.8);
backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(99, 102, 241, 0.2);
padding: 1rem 2rem;
margin: -1rem -1rem 2rem -1rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.nav-brand {
display: flex;
align-items: center;
gap: 0.75rem;
}
.nav-brand h1 {
font-size: 1.5rem;
font-weight: 700;
background: linear-gradient(135deg, #6366f1, #0ea5e9);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin: 0;
}
.nav-brand span {
font-size: 0.75rem;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.1em;
}
/* Card styling */
.modern-card {
background: rgba(30, 41, 59, 0.6);
backdrop-filter: blur(8px);
border: 1px solid rgba(99, 102, 241, 0.15);
border-radius: 16px;
padding: 1.5rem;
margin-bottom: 1rem;
transition: all 0.3s ease;
}
.modern-card:hover {
border-color: rgba(99, 102, 241, 0.4);
box-shadow: 0 8px 32px rgba(99, 102, 241, 0.15);
}
/* Metric cards */
.metric-card {
background: linear-gradient(135deg, rgba(99, 102, 241, 0.1), rgba(14, 165, 233, 0.1));
border: 1px solid rgba(99, 102, 241, 0.2);
border-radius: 12px;
padding: 1.25rem;
text-align: center;
}
.metric-value {
font-size: 2rem;
font-weight: 700;
background: linear-gradient(135deg, #6366f1, #0ea5e9);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.metric-label {
font-size: 0.875rem;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-top: 0.25rem;
}
/* Section headers */
.section-header {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid rgba(99, 102, 241, 0.3);
}
/* Input fields */
.stTextInput > div > div > input,
.stTextArea > div > div > textarea {
background: rgba(30, 41, 59, 0.8) !important;
border: 1px solid rgba(99, 102, 241, 0.3) !important;
border-radius: 10px !important;
color: var(--text-primary) !important;
padding: 0.75rem 1rem !important;
}
.stTextInput > div > div > input:focus,
.stTextArea > div > div > textarea:focus {
border-color: var(--primary) !important;
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2) !important;
}
/* Buttons */
.stButton > button {
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
color: white !important;
border: none !important;
border-radius: 10px !important;
padding: 0.75rem 1.5rem !important;
font-weight: 600 !important;
transition: all 0.3s ease !important;
box-shadow: 0 4px 14px rgba(99, 102, 241, 0.3) !important;
}
.stButton > button:hover {
transform: translateY(-2px) !important;
box-shadow: 0 6px 20px rgba(99, 102, 241, 0.4) !important;
}
/* Tabs styling */
.stTabs [data-baseweb="tab-list"] {
background: rgba(30, 41, 59, 0.6);
border-radius: 12px;
padding: 0.5rem;
gap: 0.5rem;
border: 1px solid rgba(99, 102, 241, 0.15);
}
.stTabs [data-baseweb="tab"] {
background: transparent;
border-radius: 8px;
color: var(--text-secondary);
padding: 0.75rem 1.5rem;
font-weight: 500;
}
.stTabs [aria-selected="true"] {
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
color: white !important;
}
.stTabs [data-baseweb="tab-border"] {
display: none;
}
.stTabs [data-baseweb="tab-highlight"] {
display: none;
}
/* Expander styling */
.streamlit-expanderHeader {
background: rgba(30, 41, 59, 0.6) !important;
border: 1px solid rgba(99, 102, 241, 0.15) !important;
border-radius: 10px !important;
color: var(--text-primary) !important;
}
.streamlit-expanderContent {
background: rgba(30, 41, 59, 0.4) !important;
border: 1px solid rgba(99, 102, 241, 0.1) !important;
border-top: none !important;
border-radius: 0 0 10px 10px !important;
}
/* Slider */
.stSlider > div > div > div {
background: var(--primary) !important;
}
/* Select box */
.stSelectbox > div > div {
background: rgba(30, 41, 59, 0.8) !important;
border: 1px solid rgba(99, 102, 241, 0.3) !important;
border-radius: 10px !important;
}
/* Progress bar */
.stProgress > div > div > div {
background: linear-gradient(90deg, #6366f1, #0ea5e9) !important;
}
/* Alerts */
.stAlert {
border-radius: 10px !important;
border: none !important;
}
/* Metrics */
[data-testid="stMetricValue"] {
background: linear-gradient(135deg, #6366f1, #0ea5e9);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 700;
}
[data-testid="stMetricLabel"] {
color: var(--text-secondary) !important;
}
/* Plotly charts */
.js-plotly-plot {
border-radius: 12px;
overflow: hidden;
}
/* 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;
}
.status-success {
background: rgba(16, 185, 129, 0.2);
color: #10b981;
border: 1px solid rgba(16, 185, 129, 0.3);
}
.status-warning {
background: rgba(245, 158, 11, 0.2);
color: #f59e0b;
border: 1px solid rgba(245, 158, 11, 0.3);
}
.status-error {
background: rgba(239, 68, 68, 0.2);
color: #ef4444;
border: 1px solid rgba(239, 68, 68, 0.3);
}
/* Dividers */
hr {
border: none;
border-top: 1px solid rgba(99, 102, 241, 0.2);
margin: 1.5rem 0;
}
/* Info boxes */
.info-box {
background: linear-gradient(135deg, rgba(99, 102, 241, 0.1), rgba(14, 165, 233, 0.05));
border: 1px solid rgba(99, 102, 241, 0.2);
border-radius: 12px;
padding: 1rem 1.25rem;
margin: 1rem 0;
}
/* Feature list */
.feature-item {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 0.75rem 0;
border-bottom: 1px solid rgba(99, 102, 241, 0.1);
}
.feature-icon {
color: var(--primary);
font-size: 1.25rem;
}
</style>
""", unsafe_allow_html=True)
@st.cache_resource @st.cache_resource
def get_analyzer(): def get_analyzer():
@@ -43,37 +336,44 @@ def get_db_client():
def render_header(): def render_header():
"""Render the dashboard header.""" """Render the modern dashboard header."""
st.title("SPARC Dashboard") st.markdown("""
st.markdown("**Semiconductor Patent & Analytics Report Core**") <div class="nav-container">
st.markdown("---") <div class="nav-brand">
<h1>⚡ SPARC</h1>
<span>Semiconductor Patent Analytics</span>
</div>
</div>
""", unsafe_allow_html=True)
def render_sidebar(): def render_navigation():
"""Render the sidebar with navigation and controls.""" """Render horizontal tab navigation at the top."""
st.sidebar.title("Navigation") tabs = st.tabs(["🔍 Company Analysis", "📦 Batch Analysis", "📊 Analytics", "️ About"])
page = st.sidebar.radio( return tabs
"Select Page",
["Company Analysis", "Batch Analysis", "Analytics", "About"],
)
return page
def render_company_analysis(): def render_company_analysis():
"""Render single company analysis page.""" """Render single company analysis page."""
st.header("Company Patent Analysis") st.markdown('<p class="section-header">Single Company Analysis</p>', unsafe_allow_html=True)
st.markdown("Analyze a company's patent portfolio using AI-powered insights.")
col1, col2 = st.columns([2, 1]) st.markdown("")
# Search card
with st.container():
col1, col2 = st.columns([3, 1])
with col1: with col1:
company_name = st.text_input( company_name = st.text_input(
"Company Name", "Company Name",
placeholder="e.g., nvidia, intel, amd", placeholder="Enter company name (e.g., nvidia, intel, amd)",
help="Enter the company name to analyze their patent portfolio", help="Enter the company name to analyze their patent portfolio",
label_visibility="collapsed",
) )
with col2: with col2:
analyze_btn = st.button("Analyze", type="primary", use_container_width=True) analyze_btn = st.button("🔍 Analyze", type="primary", use_container_width=True)
if analyze_btn and company_name: if analyze_btn and company_name:
with st.spinner(f"Analyzing {company_name}..."): with st.spinner(f"Analyzing {company_name}..."):
@@ -81,45 +381,57 @@ def render_company_analysis():
result = analyzer._analyze_company_safe(company_name) result = analyzer._analyze_company_safe(company_name)
if result.success: if result.success:
st.success(f"Analysis complete for {company_name}") st.success(f"Analysis complete for {company_name.upper()}")
# Metrics row st.markdown("")
# Metrics row with custom styling
col1, col2, col3 = st.columns(3) col1, col2, col3 = st.columns(3)
with col1: with col1:
st.metric("Patents Analyzed", result.patent_count) st.metric("Patents Found", result.patent_count)
with col2: with col2:
st.metric("Status", "Success") st.metric("Analysis Status", "Complete")
with col3: with col3:
st.metric("Timestamp", result.timestamp.strftime("%H:%M:%S")) st.metric("Timestamp", result.timestamp.strftime("%H:%M:%S"))
# Analysis content st.markdown("")
st.subheader("AI Analysis")
# Analysis content in a styled container
st.markdown('<p class="section-header">AI Analysis Results</p>', unsafe_allow_html=True)
with st.container():
st.markdown(result.analysis) st.markdown(result.analysis)
else: else:
st.error(f"Analysis failed: {result.error}") st.error(f"Analysis failed: {result.error}")
elif not company_name and analyze_btn:
st.warning("Please enter a company name to analyze.")
def render_batch_analysis(): def render_batch_analysis():
"""Render batch analysis page.""" """Render batch analysis page."""
st.header("Batch Company Analysis") st.markdown('<p class="section-header">Batch Company Analysis</p>', unsafe_allow_html=True)
st.markdown("Analyze multiple companies simultaneously for comparative insights.")
st.markdown( st.markdown("")
"Analyze multiple companies simultaneously. Enter company names separated by commas or newlines."
)
# Input section
col1, col2 = st.columns([2, 1])
with col1:
companies_input = st.text_area( companies_input = st.text_area(
"Company Names", "Company Names",
placeholder="nvidia\namd\nintel\nqualcomm", placeholder="Enter company names (one per line or comma-separated):\nnvidia\namd\nintel\nqualcomm",
height=150, height=150,
label_visibility="collapsed",
) )
col1, col2 = st.columns(2)
with col1:
max_workers = st.slider("Concurrent Workers", 1, 5, 3)
with col2: with col2:
st.markdown("**Configuration**")
max_workers = st.slider("Concurrent Workers", 1, 5, 3, help="Number of parallel analysis threads")
st.markdown("")
analyze_btn = st.button( analyze_btn = st.button(
"Run Batch Analysis", type="primary", use_container_width=True "🚀 Run Batch Analysis", type="primary", use_container_width=True
) )
if analyze_btn and companies_input: if analyze_btn and companies_input:
@@ -134,7 +446,7 @@ def render_batch_analysis():
st.warning("Please enter at least one company name") st.warning("Please enter at least one company name")
return return
st.info(f"Starting analysis of {len(companies)} companies...") st.info(f"🔄 Starting analysis of {len(companies)} companies...")
# Progress tracking # Progress tracking
progress_bar = st.progress(0) progress_bar = st.progress(0)
@@ -154,10 +466,12 @@ def render_batch_analysis():
) )
progress_bar.progress(1.0) progress_bar.progress(1.0)
status_text.text("Analysis complete!") status_text.text("Analysis complete!")
st.markdown("")
# Summary metrics # Summary metrics
st.subheader("Results Summary") st.markdown('<p class="section-header">Results Summary</p>', unsafe_allow_html=True)
col1, col2, col3, col4 = st.columns(4) col1, col2, col3, col4 = st.columns(4)
with col1: with col1:
st.metric("Total Companies", result.total_companies) st.metric("Total Companies", result.total_companies)
@@ -178,7 +492,7 @@ def render_batch_analysis():
df = pd.DataFrame( df = pd.DataFrame(
[ [
{ {
"Company": r.company_name, "Company": r.company_name.upper(),
"Patents": r.patent_count, "Patents": r.patent_count,
"Status": "Success" if r.success else "Failed", "Status": "Success" if r.success else "Failed",
} }
@@ -191,16 +505,34 @@ def render_batch_analysis():
x="Company", x="Company",
y="Patents", y="Patents",
color="Status", color="Status",
color_discrete_map={"Success": "#28a745", "Failed": "#dc3545"}, color_discrete_map={"Success": "#10b981", "Failed": "#ef4444"},
title="Patents per Company", title="",
)
fig.update_layout(
plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)",
font_color="#94a3b8",
legend=dict(
orientation="h",
yanchor="bottom",
y=1.02,
xanchor="right",
x=1
),
xaxis=dict(showgrid=False),
yaxis=dict(showgrid=True, gridcolor="rgba(99, 102, 241, 0.1)"),
) )
st.plotly_chart(fig, use_container_width=True) st.plotly_chart(fig, use_container_width=True)
st.markdown("")
# Individual results # Individual results
st.subheader("Individual Results") st.markdown('<p class="section-header">Detailed Results</p>', unsafe_allow_html=True)
for r in result.results: for r in result.results:
status_icon = "" if r.success else ""
status_class = "status-success" if r.success else "status-error"
with st.expander( with st.expander(
f"{'' if r.success else ''} {r.company_name} ({r.patent_count} patents)" f"{status_icon} {r.company_name.upper()} {r.patent_count} patents"
): ):
if r.success: if r.success:
st.markdown(r.analysis) st.markdown(r.analysis)
@@ -210,21 +542,28 @@ def render_batch_analysis():
def render_analytics(): def render_analytics():
"""Render analytics page with database insights.""" """Render analytics page with database insights."""
st.header("Analytics Dashboard") st.markdown('<p class="section-header">Analytics Dashboard</p>', unsafe_allow_html=True)
st.markdown("Track historical analysis data and view insights.")
db_client = get_db_client() db_client = get_db_client()
if not db_client: if not db_client:
st.warning( st.markdown("")
"Database mode is not enabled. Set USE_DATABASE=true in your .env file to enable analytics." st.markdown("""
) <div class="info-box">
st.info( <strong>⚠️ Database Not Connected</strong><br>
"Analytics features require storing analysis results in PostgreSQL for historical tracking." <span style="color: #94a3b8;">Set <code>USE_DATABASE=true</code> in your .env file to enable analytics tracking.</span>
) </div>
""", unsafe_allow_html=True)
st.info("Analytics features require storing analysis results in PostgreSQL for historical tracking.")
return return
st.markdown("")
# Time range selector # Time range selector
days = st.selectbox("Time Range", [7, 14, 30, 90], index=0) col1, col2, col3 = st.columns([1, 2, 1])
with col1:
days = st.selectbox("Time Range", [7, 14, 30, 90], index=0, format_func=lambda x: f"Last {x} days")
try: try:
analytics = db_client.get_analytics(days=days) analytics = db_client.get_analytics(days=days)
@@ -233,8 +572,9 @@ def render_analytics():
st.info("No analytics data available yet. Run some analyses first!") st.info("No analytics data available yet. Run some analyses first!")
return return
st.markdown("")
# Summary metrics # Summary metrics
st.subheader("Summary")
col1, col2, col3 = st.columns(3) col1, col2, col3 = st.columns(3)
with col1: with col1:
@@ -249,6 +589,8 @@ def render_analytics():
types = len(analytics.get("by_type", {})) types = len(analytics.get("by_type", {}))
st.metric("Analysis Types", types) st.metric("Analysis Types", types)
st.markdown("")
# Charts # Charts
col1, col2 = st.columns(2) col1, col2 = st.columns(2)
@@ -256,10 +598,17 @@ def render_analytics():
by_company = analytics.get("by_company", {}) by_company = analytics.get("by_company", {})
if by_company: if by_company:
df = pd.DataFrame( df = pd.DataFrame(
[{"Company": k, "Count": v} for k, v in by_company.items()] [{"Company": k.upper(), "Count": v} for k, v in by_company.items()]
) )
fig = px.pie( fig = px.pie(
df, values="Count", names="Company", title="Analyses by Company" df, values="Count", names="Company", title="Distribution by Company",
hole=0.4,
color_discrete_sequence=px.colors.sequential.Purp_r,
)
fig.update_layout(
plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)",
font_color="#94a3b8",
) )
st.plotly_chart(fig, use_container_width=True) st.plotly_chart(fig, use_container_width=True)
@@ -269,19 +618,29 @@ def render_analytics():
df = pd.DataFrame( df = pd.DataFrame(
[{"Type": k, "Count": v} for k, v in by_type.items()] [{"Type": k, "Count": v} for k, v in by_type.items()]
) )
fig = px.bar(df, x="Type", y="Count", title="Analyses by Type") fig = px.bar(df, x="Type", y="Count", title="Analysis Types",
color_discrete_sequence=["#6366f1"])
fig.update_layout(
plot_bgcolor="rgba(0,0,0,0)",
paper_bgcolor="rgba(0,0,0,0)",
font_color="#94a3b8",
xaxis=dict(showgrid=False),
yaxis=dict(showgrid=True, gridcolor="rgba(99, 102, 241, 0.1)"),
)
st.plotly_chart(fig, use_container_width=True) st.plotly_chart(fig, use_container_width=True)
st.markdown("")
# Recent messages # Recent messages
st.subheader("Recent Analyses") st.markdown('<p class="section-header">Recent Analyses</p>', unsafe_allow_html=True)
messages = db_client.get_messages(limit=10) messages = db_client.get_messages(limit=10)
if messages: if messages:
for msg in messages: for msg in messages:
with st.expander( with st.expander(
f"{msg.get('company_name', 'Unknown')} - {msg.get('analysis_type', 'N/A')} ({msg.get('timestamp', 'N/A')})" f"📄 {msg.get('company_name', 'Unknown').upper()} {msg.get('analysis_type', 'N/A')} ({msg.get('timestamp', 'N/A')})"
): ):
st.markdown(f"**Model:** {msg.get('model', 'N/A')}") st.markdown(f"**Model:** `{msg.get('model', 'N/A')}`")
if msg.get("response"): if msg.get("response"):
st.markdown(msg["response"][:500] + "...") st.markdown(msg["response"][:500] + "...")
@@ -291,70 +650,127 @@ def render_analytics():
def render_about(): def render_about():
"""Render about page.""" """Render about page."""
st.header("About SPARC") st.markdown('<p class="section-header">About SPARC</p>', unsafe_allow_html=True)
st.markdown( col1, col2 = st.columns([2, 1])
"""
**SPARC** (Semiconductor Patent & Analytics Report Core) is a patent analysis
system that estimates company performance by analyzing their patent portfolios
using LLM-powered insights.
### Features with col1:
st.markdown("""
**SPARC** (Semiconductor Patent & Analytics Report Core) is an AI-powered patent analysis
platform that evaluates company performance by analyzing their patent portfolios
with cutting-edge language models.
""")
- **Patent Retrieval**: Automated collection via SerpAPI's Google Patents engine st.markdown("")
- **Intelligent Parsing**: Extracts key sections from patent PDFs st.markdown("**Key Features**")
- **AI Analysis**: Uses Claude 3.5 Sonnet for deep analysis
- **Batch Processing**: Analyze multiple companies concurrently
- **REST API**: FastAPI web service for integration
- **Analytics**: Track and visualize analysis history
### Technology Stack features = [
("🔍", "Patent Retrieval", "Automated collection via SerpAPI's Google Patents"),
("📄", "Intelligent Parsing", "Extracts key sections from patent documents"),
("🤖", "AI Analysis", "Deep analysis powered by Claude 3.5 Sonnet"),
("", "Batch Processing", "Analyze multiple companies concurrently"),
("🌐", "REST API", "FastAPI web service for seamless integration"),
("📊", "Analytics", "Track and visualize historical analysis data"),
]
- **Backend**: Python, FastAPI for icon, title, desc in features:
- **AI**: Claude 3.5 Sonnet via OpenRouter st.markdown(f"""
- **Database**: PostgreSQL <div class="feature-item">
- **Dashboard**: Streamlit, Plotly <span class="feature-icon">{icon}</span>
- **Patent Data**: SerpAPI Google Patents <div>
<strong>{title}</strong><br>
<span style="color: #94a3b8; font-size: 0.875rem;">{desc}</span>
</div>
</div>
""", unsafe_allow_html=True)
### Links with col2:
st.markdown("**Technology Stack**")
st.markdown("""
<div class="info-box">
<div style="display: grid; gap: 0.5rem;">
<div><span style="color: #6366f1;">Backend</span><br><span style="color: #94a3b8;">Python, FastAPI</span></div>
<div><span style="color: #6366f1;">AI Model</span><br><span style="color: #94a3b8;">Claude 3.5 Sonnet</span></div>
<div><span style="color: #6366f1;">Database</span><br><span style="color: #94a3b8;">PostgreSQL</span></div>
<div><span style="color: #6366f1;">Dashboard</span><br><span style="color: #94a3b8;">Streamlit, Plotly</span></div>
<div><span style="color: #6366f1;">Data Source</span><br><span style="color: #94a3b8;">SerpAPI Patents</span></div>
</div>
</div>
""", unsafe_allow_html=True)
- API Docs: `http://localhost:8000/docs` st.markdown("")
- Health Check: `http://localhost:8000/health` st.markdown("**API Endpoints**")
""" st.code("http://localhost:8000/docs", language=None)
) st.code("http://localhost:8000/health", language=None)
st.markdown("")
st.markdown("")
# System status # System status
st.subheader("System Status") st.markdown('<p class="section-header">System Status</p>', unsafe_allow_html=True)
col1, col2 = st.columns(2) col1, col2, col3 = st.columns(3)
with col1: with col1:
db_client = get_db_client() db_client = get_db_client()
if db_client: if db_client:
st.success("Database: Connected") st.markdown("""
<div class="metric-card">
<div style="color: #10b981; font-size: 1.5rem;">●</div>
<div class="metric-label">Database</div>
<div style="color: #10b981; font-weight: 600;">Connected</div>
</div>
""", unsafe_allow_html=True)
else: else:
st.warning("Database: Not configured") st.markdown("""
<div class="metric-card">
<div style="color: #f59e0b; font-size: 1.5rem;">●</div>
<div class="metric-label">Database</div>
<div style="color: #f59e0b; font-weight: 600;">Not Configured</div>
</div>
""", unsafe_allow_html=True)
with col2: with col2:
analyzer = get_analyzer() analyzer = get_analyzer()
if analyzer: if analyzer:
st.success("Analyzer: Ready") st.markdown("""
<div class="metric-card">
<div style="color: #10b981; font-size: 1.5rem;">●</div>
<div class="metric-label">Analyzer</div>
<div style="color: #10b981; font-weight: 600;">Ready</div>
</div>
""", unsafe_allow_html=True)
else: else:
st.error("Analyzer: Not initialized") st.markdown("""
<div class="metric-card">
<div style="color: #ef4444; font-size: 1.5rem;">●</div>
<div class="metric-label">Analyzer</div>
<div style="color: #ef4444; font-weight: 600;">Not Initialized</div>
</div>
""", unsafe_allow_html=True)
with col3:
st.markdown("""
<div class="metric-card">
<div style="color: #10b981; font-size: 1.5rem;">●</div>
<div class="metric-label">Dashboard</div>
<div style="color: #10b981; font-weight: 600;">Online</div>
</div>
""", unsafe_allow_html=True)
def main(): def main():
"""Main dashboard entry point.""" """Main dashboard entry point."""
render_header() render_header()
page = render_sidebar() tabs = render_navigation()
if page == "Company Analysis": with tabs[0]:
render_company_analysis() render_company_analysis()
elif page == "Batch Analysis": with tabs[1]:
render_batch_analysis() render_batch_analysis()
elif page == "Analytics": with tabs[2]:
render_analytics() render_analytics()
elif page == "About": with tabs[3]:
render_about() render_about()