From 7eb72ab54967d7189e27c06650adf854a4307cb7 Mon Sep 17 00:00:00 2001 From: 0xWheatyz Date: Fri, 13 Mar 2026 15:36:56 -0400 Subject: [PATCH] feat: redesign dashboard with modern UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- dashboard.py | 618 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 517 insertions(+), 101 deletions(-) diff --git a/dashboard.py b/dashboard.py index 1994a56..af9f001 100644 --- a/dashboard.py +++ b/dashboard.py @@ -17,11 +17,304 @@ from SPARC import config st.set_page_config( page_title="SPARC Dashboard", - page_icon="📊", + page_icon="⚡", layout="wide", - initial_sidebar_state="expanded", + initial_sidebar_state="collapsed", ) +# Modern CSS styling +st.markdown(""" + +""", unsafe_allow_html=True) + @st.cache_resource def get_analyzer(): @@ -43,37 +336,44 @@ def get_db_client(): def render_header(): - """Render the dashboard header.""" - st.title("SPARC Dashboard") - st.markdown("**Semiconductor Patent & Analytics Report Core**") - st.markdown("---") + """Render the modern dashboard header.""" + st.markdown(""" + + """, unsafe_allow_html=True) -def render_sidebar(): - """Render the sidebar with navigation and controls.""" - st.sidebar.title("Navigation") - page = st.sidebar.radio( - "Select Page", - ["Company Analysis", "Batch Analysis", "Analytics", "About"], - ) - return page +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.header("Company Patent Analysis") + st.markdown('

Single Company Analysis

', unsafe_allow_html=True) + st.markdown("Analyze a company's patent portfolio using AI-powered insights.") - col1, col2 = st.columns([2, 1]) + st.markdown("") - with col1: - company_name = st.text_input( - "Company Name", - placeholder="e.g., nvidia, intel, amd", - help="Enter the company name to analyze their patent portfolio", - ) + # Search card + with st.container(): + col1, col2 = st.columns([3, 1]) - with col2: - analyze_btn = st.button("Analyze", type="primary", use_container_width=True) + 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}..."): @@ -81,45 +381,57 @@ def render_company_analysis(): result = analyzer._analyze_company_safe(company_name) 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) with col1: - st.metric("Patents Analyzed", result.patent_count) + st.metric("Patents Found", result.patent_count) with col2: - st.metric("Status", "Success") + st.metric("Analysis Status", "Complete") with col3: st.metric("Timestamp", result.timestamp.strftime("%H:%M:%S")) - # Analysis content - st.subheader("AI Analysis") - st.markdown(result.analysis) + st.markdown("") + + # Analysis content in a styled container + st.markdown('

AI Analysis Results

', 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.header("Batch Company Analysis") + st.markdown('

Batch Company Analysis

', unsafe_allow_html=True) + st.markdown("Analyze multiple companies simultaneously for comparative insights.") - st.markdown( - "Analyze multiple companies simultaneously. Enter company names separated by commas or newlines." - ) + st.markdown("") - companies_input = st.text_area( - "Company Names", - placeholder="nvidia\namd\nintel\nqualcomm", - height=150, - ) + # Input section + col1, col2 = st.columns([2, 1]) - col1, col2 = st.columns(2) with col1: - max_workers = st.slider("Concurrent Workers", 1, 5, 3) + 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 + "🚀 Run Batch Analysis", type="primary", use_container_width=True ) if analyze_btn and companies_input: @@ -134,7 +446,7 @@ def render_batch_analysis(): st.warning("Please enter at least one company name") return - st.info(f"Starting analysis of {len(companies)} companies...") + st.info(f"🔄 Starting analysis of {len(companies)} companies...") # Progress tracking progress_bar = st.progress(0) @@ -154,10 +466,12 @@ def render_batch_analysis(): ) progress_bar.progress(1.0) - status_text.text("Analysis complete!") + status_text.text("✓ Analysis complete!") + + st.markdown("") # Summary metrics - st.subheader("Results Summary") + st.markdown('

Results Summary

', unsafe_allow_html=True) col1, col2, col3, col4 = st.columns(4) with col1: st.metric("Total Companies", result.total_companies) @@ -178,7 +492,7 @@ def render_batch_analysis(): df = pd.DataFrame( [ { - "Company": r.company_name, + "Company": r.company_name.upper(), "Patents": r.patent_count, "Status": "Success" if r.success else "Failed", } @@ -191,16 +505,34 @@ def render_batch_analysis(): x="Company", y="Patents", color="Status", - color_discrete_map={"Success": "#28a745", "Failed": "#dc3545"}, - title="Patents per Company", + 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.subheader("Individual Results") + st.markdown('

Detailed Results

', 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"{'✓' 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: st.markdown(r.analysis) @@ -210,21 +542,28 @@ def render_batch_analysis(): def render_analytics(): """Render analytics page with database insights.""" - st.header("Analytics Dashboard") + st.markdown('

Analytics Dashboard

', unsafe_allow_html=True) + st.markdown("Track historical analysis data and view insights.") db_client = get_db_client() if not db_client: - st.warning( - "Database mode is not enabled. Set USE_DATABASE=true in your .env file to enable analytics." - ) - st.info( - "Analytics features require storing analysis results in PostgreSQL for historical tracking." - ) + st.markdown("") + st.markdown(""" +
+ âš ī¸ Database Not Connected
+ Set USE_DATABASE=true in your .env file to enable analytics tracking. +
+ """, unsafe_allow_html=True) + st.info("Analytics features require storing analysis results in PostgreSQL for historical tracking.") return + st.markdown("") + # 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: 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!") return + st.markdown("") + # Summary metrics - st.subheader("Summary") col1, col2, col3 = st.columns(3) with col1: @@ -249,6 +589,8 @@ def render_analytics(): types = len(analytics.get("by_type", {})) st.metric("Analysis Types", types) + st.markdown("") + # Charts col1, col2 = st.columns(2) @@ -256,10 +598,17 @@ def render_analytics(): by_company = analytics.get("by_company", {}) if by_company: 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( - 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) @@ -269,19 +618,29 @@ def render_analytics(): df = pd.DataFrame( [{"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.markdown("") + # Recent messages - st.subheader("Recent Analyses") + st.markdown('

Recent Analyses

', 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')} - {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"): st.markdown(msg["response"][:500] + "...") @@ -291,70 +650,127 @@ def render_analytics(): def render_about(): """Render about page.""" - st.header("About SPARC") + st.markdown('

About SPARC

', unsafe_allow_html=True) - st.markdown( - """ - **SPARC** (Semiconductor Patent & Analytics Report Core) is a patent analysis - system that estimates company performance by analyzing their patent portfolios - using LLM-powered insights. + col1, col2 = st.columns([2, 1]) - ### 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 - - **Intelligent Parsing**: Extracts key sections from patent PDFs - - **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 + st.markdown("") + st.markdown("**Key Features**") - ### 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 - - **AI**: Claude 3.5 Sonnet via OpenRouter - - **Database**: PostgreSQL - - **Dashboard**: Streamlit, Plotly - - **Patent Data**: SerpAPI Google Patents + for icon, title, desc in features: + st.markdown(f""" +
+ {icon} +
+ {title}
+ {desc} +
+
+ """, unsafe_allow_html=True) - ### Links + with col2: + st.markdown("**Technology Stack**") + st.markdown(""" +
+
+
Backend
Python, FastAPI
+
AI Model
Claude 3.5 Sonnet
+
Database
PostgreSQL
+
Dashboard
Streamlit, Plotly
+
Data Source
SerpAPI Patents
+
+
+ """, unsafe_allow_html=True) - - API Docs: `http://localhost:8000/docs` - - Health Check: `http://localhost:8000/health` - """ - ) + 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.subheader("System Status") + st.markdown('

System Status

', unsafe_allow_html=True) - col1, col2 = st.columns(2) + col1, col2, col3 = st.columns(3) with col1: db_client = get_db_client() if db_client: - st.success("Database: Connected") + st.markdown(""" +
+
●
+
Database
+
Connected
+
+ """, unsafe_allow_html=True) else: - st.warning("Database: Not configured") + st.markdown(""" +
+
●
+
Database
+
Not Configured
+
+ """, unsafe_allow_html=True) with col2: analyzer = get_analyzer() if analyzer: - st.success("Analyzer: Ready") + st.markdown(""" +
+
●
+
Analyzer
+
Ready
+
+ """, unsafe_allow_html=True) else: - st.error("Analyzer: Not initialized") + st.markdown(""" +
+
●
+
Analyzer
+
Not Initialized
+
+ """, unsafe_allow_html=True) + + with col3: + st.markdown(""" +
+
●
+
Dashboard
+
Online
+
+ """, unsafe_allow_html=True) def main(): """Main dashboard entry point.""" render_header() - page = render_sidebar() + tabs = render_navigation() - if page == "Company Analysis": + with tabs[0]: render_company_analysis() - elif page == "Batch Analysis": + with tabs[1]: render_batch_analysis() - elif page == "Analytics": + with tabs[2]: render_analytics() - elif page == "About": + with tabs[3]: render_about()