chore: remove deprecated Streamlit dashboard
Dashboard functionality replaced by React frontend in frontend/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
-778
@@ -1,778 +0,0 @@
|
||||
"""SPARC Visualization Dashboard.
|
||||
|
||||
A Streamlit-based dashboard for visualizing patent analysis results.
|
||||
Run with: streamlit run dashboard.py
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import plotly.express as px
|
||||
import plotly.graph_objects as go
|
||||
import pandas as pd
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from SPARC.analyzer import CompanyAnalyzer
|
||||
from SPARC.database import DatabaseClient
|
||||
from SPARC import config
|
||||
|
||||
|
||||
st.set_page_config(
|
||||
page_title="SPARC Dashboard",
|
||||
page_icon="⚡",
|
||||
layout="wide",
|
||||
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
|
||||
def get_analyzer():
|
||||
"""Get or create the CompanyAnalyzer instance."""
|
||||
return CompanyAnalyzer()
|
||||
|
||||
|
||||
@st.cache_resource
|
||||
def get_db_client():
|
||||
"""Get database client if available."""
|
||||
if config.use_database:
|
||||
try:
|
||||
client = DatabaseClient()
|
||||
client.connect()
|
||||
return client
|
||||
except Exception:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def render_header():
|
||||
"""Render the modern dashboard header."""
|
||||
st.markdown("""
|
||||
<div class="nav-container">
|
||||
<div class="nav-brand">
|
||||
<h1>⚡ SPARC</h1>
|
||||
<span>Semiconductor Patent Analytics</span>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
|
||||
def render_navigation():
|
||||
"""Render horizontal tab navigation at the top."""
|
||||
tabs = st.tabs(["🔍 Company Analysis", "📦 Batch Analysis", "📊 Analytics", "ℹ️ About"])
|
||||
return tabs
|
||||
|
||||
|
||||
def render_company_analysis():
|
||||
"""Render single company analysis page."""
|
||||
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.")
|
||||
|
||||
st.markdown("")
|
||||
|
||||
# Search card
|
||||
with st.container():
|
||||
col1, col2 = st.columns([3, 1])
|
||||
|
||||
with col1:
|
||||
company_name = st.text_input(
|
||||
"Company Name",
|
||||
placeholder="Enter company name (e.g., nvidia, intel, amd)",
|
||||
help="Enter the company name to analyze their patent portfolio",
|
||||
label_visibility="collapsed",
|
||||
)
|
||||
|
||||
with col2:
|
||||
analyze_btn = st.button("🔍 Analyze", type="primary", use_container_width=True)
|
||||
|
||||
if analyze_btn and company_name:
|
||||
with st.spinner(f"Analyzing {company_name}..."):
|
||||
analyzer = get_analyzer()
|
||||
result = analyzer._analyze_company_safe(company_name)
|
||||
|
||||
if result.success:
|
||||
st.success(f"✓ Analysis complete for {company_name.upper()}")
|
||||
|
||||
st.markdown("")
|
||||
|
||||
# Metrics row with custom styling
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
st.metric("Patents Found", result.patent_count)
|
||||
with col2:
|
||||
st.metric("Analysis Status", "Complete")
|
||||
with col3:
|
||||
st.metric("Timestamp", result.timestamp.strftime("%H:%M:%S"))
|
||||
|
||||
st.markdown("")
|
||||
|
||||
# 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)
|
||||
|
||||
else:
|
||||
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():
|
||||
"""Render batch analysis page."""
|
||||
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("")
|
||||
|
||||
# Input section
|
||||
col1, col2 = st.columns([2, 1])
|
||||
|
||||
with col1:
|
||||
companies_input = st.text_area(
|
||||
"Company Names",
|
||||
placeholder="Enter company names (one per line or comma-separated):\nnvidia\namd\nintel\nqualcomm",
|
||||
height=150,
|
||||
label_visibility="collapsed",
|
||||
)
|
||||
|
||||
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(
|
||||
"🚀 Run Batch Analysis", type="primary", use_container_width=True
|
||||
)
|
||||
|
||||
if analyze_btn and companies_input:
|
||||
# Parse company names
|
||||
companies = [
|
||||
c.strip()
|
||||
for c in companies_input.replace(",", "\n").split("\n")
|
||||
if c.strip()
|
||||
]
|
||||
|
||||
if not companies:
|
||||
st.warning("Please enter at least one company name")
|
||||
return
|
||||
|
||||
st.info(f"🔄 Starting analysis of {len(companies)} companies...")
|
||||
|
||||
# Progress tracking
|
||||
progress_bar = st.progress(0)
|
||||
status_text = st.empty()
|
||||
|
||||
analyzer = get_analyzer()
|
||||
|
||||
def update_progress(company: str, completed: int, total: int):
|
||||
progress = completed / total
|
||||
progress_bar.progress(progress)
|
||||
status_text.text(f"Analyzing {company}... ({completed}/{total})")
|
||||
|
||||
result = analyzer.analyze_companies(
|
||||
companies=companies,
|
||||
max_workers=max_workers,
|
||||
progress_callback=update_progress,
|
||||
)
|
||||
|
||||
progress_bar.progress(1.0)
|
||||
status_text.text("✓ Analysis complete!")
|
||||
|
||||
st.markdown("")
|
||||
|
||||
# Summary metrics
|
||||
st.markdown('<p class="section-header">Results Summary</p>', unsafe_allow_html=True)
|
||||
col1, col2, col3, col4 = st.columns(4)
|
||||
with col1:
|
||||
st.metric("Total Companies", result.total_companies)
|
||||
with col2:
|
||||
st.metric("Successful", result.successful)
|
||||
with col3:
|
||||
st.metric("Failed", result.failed)
|
||||
with col4:
|
||||
success_rate = (
|
||||
(result.successful / result.total_companies * 100)
|
||||
if result.total_companies > 0
|
||||
else 0
|
||||
)
|
||||
st.metric("Success Rate", f"{success_rate:.1f}%")
|
||||
|
||||
# Results chart
|
||||
if result.results:
|
||||
df = pd.DataFrame(
|
||||
[
|
||||
{
|
||||
"Company": r.company_name.upper(),
|
||||
"Patents": r.patent_count,
|
||||
"Status": "Success" if r.success else "Failed",
|
||||
}
|
||||
for r in result.results
|
||||
]
|
||||
)
|
||||
|
||||
fig = px.bar(
|
||||
df,
|
||||
x="Company",
|
||||
y="Patents",
|
||||
color="Status",
|
||||
color_discrete_map={"Success": "#10b981", "Failed": "#ef4444"},
|
||||
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.markdown("")
|
||||
|
||||
# Individual results
|
||||
st.markdown('<p class="section-header">Detailed Results</p>', unsafe_allow_html=True)
|
||||
for r in result.results:
|
||||
status_icon = "✓" if r.success else "✗"
|
||||
status_class = "status-success" if r.success else "status-error"
|
||||
with st.expander(
|
||||
f"{status_icon} {r.company_name.upper()} — {r.patent_count} patents"
|
||||
):
|
||||
if r.success:
|
||||
st.markdown(r.analysis)
|
||||
else:
|
||||
st.error(r.error)
|
||||
|
||||
|
||||
def render_analytics():
|
||||
"""Render analytics page with database insights."""
|
||||
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()
|
||||
|
||||
if not db_client:
|
||||
st.markdown("")
|
||||
st.markdown("""
|
||||
<div class="info-box">
|
||||
<strong>⚠️ Database Not Connected</strong><br>
|
||||
<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
|
||||
|
||||
st.markdown("")
|
||||
|
||||
# Time range selector
|
||||
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:
|
||||
analytics = db_client.get_analytics(days=days)
|
||||
|
||||
if not analytics:
|
||||
st.info("No analytics data available yet. Run some analyses first!")
|
||||
return
|
||||
|
||||
st.markdown("")
|
||||
|
||||
# Summary metrics
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
total = analytics.get("total_messages", 0)
|
||||
st.metric("Total Analyses", total)
|
||||
|
||||
with col2:
|
||||
companies = len(analytics.get("by_company", {}))
|
||||
st.metric("Companies Analyzed", companies)
|
||||
|
||||
with col3:
|
||||
types = len(analytics.get("by_type", {}))
|
||||
st.metric("Analysis Types", types)
|
||||
|
||||
st.markdown("")
|
||||
|
||||
# Charts
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
with col1:
|
||||
by_company = analytics.get("by_company", {})
|
||||
if by_company:
|
||||
df = pd.DataFrame(
|
||||
[{"Company": k.upper(), "Count": v} for k, v in by_company.items()]
|
||||
)
|
||||
fig = px.pie(
|
||||
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)
|
||||
|
||||
with col2:
|
||||
by_type = analytics.get("by_type", {})
|
||||
if by_type:
|
||||
df = pd.DataFrame(
|
||||
[{"Type": k, "Count": v} for k, v in by_type.items()]
|
||||
)
|
||||
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.markdown("")
|
||||
|
||||
# Recent messages
|
||||
st.markdown('<p class="section-header">Recent Analyses</p>', unsafe_allow_html=True)
|
||||
messages = db_client.get_messages(limit=10)
|
||||
|
||||
if messages:
|
||||
for msg in messages:
|
||||
with st.expander(
|
||||
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')}`")
|
||||
if msg.get("response"):
|
||||
st.markdown(msg["response"][:500] + "...")
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error fetching analytics: {e}")
|
||||
|
||||
|
||||
def render_about():
|
||||
"""Render about page."""
|
||||
st.markdown('<p class="section-header">About SPARC</p>', unsafe_allow_html=True)
|
||||
|
||||
col1, col2 = st.columns([2, 1])
|
||||
|
||||
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.
|
||||
""")
|
||||
|
||||
st.markdown("")
|
||||
st.markdown("**Key Features**")
|
||||
|
||||
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"),
|
||||
]
|
||||
|
||||
for icon, title, desc in features:
|
||||
st.markdown(f"""
|
||||
<div class="feature-item">
|
||||
<span class="feature-icon">{icon}</span>
|
||||
<div>
|
||||
<strong>{title}</strong><br>
|
||||
<span style="color: #94a3b8; font-size: 0.875rem;">{desc}</span>
|
||||
</div>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
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)
|
||||
|
||||
st.markdown("")
|
||||
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
|
||||
st.markdown('<p class="section-header">System Status</p>', unsafe_allow_html=True)
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
|
||||
with col1:
|
||||
db_client = get_db_client()
|
||||
if db_client:
|
||||
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:
|
||||
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:
|
||||
analyzer = get_analyzer()
|
||||
if analyzer:
|
||||
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:
|
||||
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():
|
||||
"""Main dashboard entry point."""
|
||||
render_header()
|
||||
tabs = render_navigation()
|
||||
|
||||
with tabs[0]:
|
||||
render_company_analysis()
|
||||
with tabs[1]:
|
||||
render_batch_analysis()
|
||||
with tabs[2]:
|
||||
render_analytics()
|
||||
with tabs[3]:
|
||||
render_about()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user