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()