Spaces:
Sleeping
Sleeping
| """ | |
| UI components for Streamlit application | |
| """ | |
| import streamlit as st | |
| import pandas as pd | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| from typing import Dict, Optional | |
| from config import ( | |
| AVAILABLE_MODELS, | |
| DEFAULT_MODEL, | |
| DEFAULT_TEMPERATURE, | |
| DEFAULT_MAX_TOKENS, | |
| DEFAULT_SAMPLE_COUNT, | |
| SAMPLE_COUNT_OPTIONS, | |
| SIDEBAR_TITLE | |
| ) | |
| def render_sidebar() -> Dict: | |
| """ | |
| Render the sidebar with configuration options | |
| Returns: | |
| Dictionary with configuration values | |
| """ | |
| st.sidebar.title(SIDEBAR_TITLE) | |
| # API Key section | |
| st.sidebar.subheader("π OpenAI API Key") | |
| # Initialize session state for API key if not exists | |
| if "api_key" not in st.session_state: | |
| st.session_state.api_key = "" | |
| # Initialize available models in session state | |
| if "available_models" not in st.session_state: | |
| st.session_state.available_models = AVAILABLE_MODELS | |
| api_key_input = st.sidebar.text_input( | |
| "API Key", | |
| value=st.session_state.api_key, | |
| type="password", | |
| help="Enter your OpenAI API key", | |
| label_visibility="collapsed" | |
| ) | |
| # Save/Validate button | |
| col1, col2 = st.sidebar.columns([1, 1]) | |
| with col1: | |
| if st.button("πΎ Save & Validate API Key", use_container_width=True): | |
| st.session_state.api_key = api_key_input | |
| st.session_state.api_key_validated = False # Reset validation | |
| st.rerun() | |
| st.sidebar.divider() | |
| # Model and Parameters section | |
| st.sidebar.subheader("π€ Model & Parameters") | |
| # Use models from session state | |
| available_models = st.session_state.available_models | |
| # Model selection | |
| model = st.sidebar.selectbox( | |
| "Model", | |
| options=available_models, | |
| index=available_models.index(DEFAULT_MODEL) if DEFAULT_MODEL in available_models else 0, | |
| help="Select the OpenAI model to use" | |
| ) | |
| # Refresh models button | |
| if st.sidebar.button("π Refresh Models", help="Fetch available models from your API key"): | |
| st.session_state.refresh_models = True | |
| st.rerun() | |
| # Temperature | |
| temperature = st.sidebar.slider( | |
| "Temperature", | |
| min_value=0.0, | |
| max_value=2.0, | |
| value=DEFAULT_TEMPERATURE, | |
| step=0.1, | |
| help="Higher values make output more random, lower values more deterministic" | |
| ) | |
| # Max Tokens | |
| max_tokens = st.sidebar.number_input( | |
| "Max Tokens", | |
| min_value=100, | |
| max_value=4000, | |
| value=DEFAULT_MAX_TOKENS, | |
| step=100, | |
| help="Maximum number of tokens in the response" | |
| ) | |
| # Sample count | |
| sample_count = st.sidebar.selectbox( | |
| "Number of Samples (responses)", | |
| options=SAMPLE_COUNT_OPTIONS, | |
| index=SAMPLE_COUNT_OPTIONS.index(DEFAULT_SAMPLE_COUNT) if DEFAULT_SAMPLE_COUNT in SAMPLE_COUNT_OPTIONS else 2, | |
| help="Number of LLM responses to generate for analysis" | |
| ) | |
| return { | |
| "api_key": st.session_state.api_key, | |
| "model": model, | |
| "temperature": temperature, | |
| "max_tokens": max_tokens, | |
| "sample_count": sample_count, | |
| } | |
| def render_summary_stats(stats: Dict): | |
| """Render summary statistics in metric cards""" | |
| col1, col2, col3, col4 = st.columns(4) | |
| with col1: | |
| st.metric("Total Responses", stats["total_responses"]) | |
| with col2: | |
| st.metric("Total Mentions", stats["total_mentions"]) | |
| with col3: | |
| st.metric("Unique Brands", stats["unique_brands"]) | |
| with col4: | |
| st.metric("Avg Brands/Response", f"{stats['avg_brands_per_response']:.1f}") | |
| def render_brands_ranking_table(df: pd.DataFrame): | |
| """Render the brands ranking table""" | |
| st.subheader("π Brand Ranking") | |
| # Display dataframe with full width | |
| st.dataframe( | |
| df, | |
| use_container_width=True, | |
| hide_index=True, | |
| column_config={ | |
| "Brand": st.column_config.TextColumn("Brand", width="medium"), | |
| "Visibility Rate": st.column_config.TextColumn("Visibility %", width="small"), | |
| "Top-1 Share": st.column_config.TextColumn("Top-1 %", width="small"), | |
| "Avg Position": st.column_config.TextColumn("Avg Pos", width="small"), | |
| "Mention Share": st.column_config.TextColumn("Mention %", width="small"), | |
| "Total Mentions": st.column_config.NumberColumn("Mentions", width="small"), | |
| "Appearances": st.column_config.NumberColumn("Appears", width="small"), | |
| } | |
| ) | |
| def render_visibility_chart(df: pd.DataFrame, top_n: int = 10): | |
| """Render visibility rate bar chart""" | |
| st.subheader(f"π Top {top_n} Brands by Visibility") | |
| # Take top N brands | |
| df_top = df.head(top_n).copy() | |
| # Convert percentage strings to floats for plotting | |
| df_top["Visibility_Value"] = df_top["Visibility Rate"].str.rstrip("%").astype(float) | |
| # Create bar chart | |
| fig = px.bar( | |
| df_top, | |
| x="Visibility_Value", | |
| y="Brand", | |
| orientation="h", | |
| labels={"Visibility_Value": "Visibility Rate (%)", "Brand": "Brand"}, | |
| text="Visibility Rate", | |
| color="Visibility_Value", | |
| color_continuous_scale="Blues" | |
| ) | |
| fig.update_layout( | |
| showlegend=False, | |
| height=400, | |
| yaxis={"categoryorder": "total ascending"} | |
| ) | |
| fig.update_traces(textposition="outside") | |
| st.plotly_chart(fig, use_container_width=True) | |
| def render_comparison_chart(df: pd.DataFrame, top_n: int = 10): | |
| """Render comparison chart with multiple metrics""" | |
| st.subheader(f"π Multi-Metric Comparison (Top {top_n})") | |
| # Take top N brands | |
| df_top = df.head(top_n).copy() | |
| # Convert percentage strings to floats | |
| df_top["Visibility_Value"] = df_top["Visibility Rate"].str.rstrip("%").astype(float) | |
| df_top["Top1_Value"] = df_top["Top-1 Share"].str.rstrip("%").astype(float) | |
| df_top["Mention_Value"] = df_top["Mention Share"].str.rstrip("%").astype(float) | |
| # Create grouped bar chart | |
| fig = go.Figure() | |
| fig.add_trace(go.Bar( | |
| name="Visibility Rate", | |
| x=df_top["Brand"], | |
| y=df_top["Visibility_Value"], | |
| text=df_top["Visibility Rate"], | |
| textposition="outside", | |
| )) | |
| fig.add_trace(go.Bar( | |
| name="Top-1 Share", | |
| x=df_top["Brand"], | |
| y=df_top["Top1_Value"], | |
| text=df_top["Top-1 Share"], | |
| textposition="outside", | |
| )) | |
| fig.add_trace(go.Bar( | |
| name="Mention Share", | |
| x=df_top["Brand"], | |
| y=df_top["Mention_Value"], | |
| text=df_top["Mention Share"], | |
| textposition="outside", | |
| )) | |
| fig.update_layout( | |
| barmode="group", | |
| xaxis_title="Brand", | |
| yaxis_title="Percentage (%)", | |
| height=500, | |
| legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1) | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| def export_results_csv(df: pd.DataFrame, stats: Dict, filename: str = "brand_analysis") -> str: | |
| """ | |
| Export results to CSV format | |
| Returns: | |
| CSV string | |
| """ | |
| # Add summary stats as header comments | |
| csv_content = f"# LLM Brand Visibility Analysis\n" | |
| csv_content += f"# Total Responses: {stats['total_responses']}\n" | |
| csv_content += f"# Total Mentions: {stats['total_mentions']}\n" | |
| csv_content += f"# Unique Brands: {stats['unique_brands']}\n" | |
| csv_content += f"# Avg Brands per Response: {stats['avg_brands_per_response']:.2f}\n" | |
| csv_content += "#\n" | |
| # Add dataframe | |
| csv_content += df.to_csv(index=False) | |
| return csv_content | |
| def export_results_txt(df: pd.DataFrame, stats: Dict) -> str: | |
| """ | |
| Export results to TXT format | |
| Returns: | |
| TXT string | |
| """ | |
| txt_content = "=" * 60 + "\n" | |
| txt_content += "LLM BRAND VISIBILITY ANALYSIS REPORT\n" | |
| txt_content += "=" * 60 + "\n\n" | |
| # Summary statistics | |
| txt_content += "SUMMARY STATISTICS:\n" | |
| txt_content += "-" * 60 + "\n" | |
| txt_content += f"Total Responses: {stats['total_responses']}\n" | |
| txt_content += f"Total Mentions: {stats['total_mentions']}\n" | |
| txt_content += f"Unique Brands: {stats['unique_brands']}\n" | |
| txt_content += f"Avg Brands per Response: {stats['avg_brands_per_response']:.2f}\n" | |
| txt_content += "\n" | |
| # Brand ranking | |
| txt_content += "BRAND RANKING:\n" | |
| txt_content += "-" * 60 + "\n" | |
| txt_content += df.to_string(index=False) | |
| txt_content += "\n\n" | |
| txt_content += "=" * 60 + "\n" | |
| txt_content += "End of Report\n" | |
| txt_content += "=" * 60 + "\n" | |
| return txt_content | |