""" Theme management for the AI Database Assistant. Handles CSS injection and theme-specific styling. """ import streamlit as st from typing import Dict class ThemeManager: """Manages themes and CSS styling for the application.""" def __init__(self): self.themes = { "Light": self._get_light_theme(), "Dark": self._get_dark_theme(), "Custom": self._get_custom_theme() } def _get_light_theme(self) -> str: """Returns CSS for light theme.""" return """ /* Light Theme - Clean and Professional */ body, .stApp { background: #f8fafc !important; color: #1f2937 !important; } /* Ensure all text is dark in light mode */ div, p, span, label, h1, h2, h3, h4, h5, h6 { color: #1f2937 !important; } /* Professional header colors for light theme */ .main-header h1 { color: #1f2937 !important; } .main-header p { color: #6b7280 !important; } .chat-container { max-width: 900px; margin: auto; } .user-bubble { background: linear-gradient(90deg, #e3f2fd, #ffffff); color: #222 !important; float: right; clear: both; } .ai-bubble { background: linear-gradient(90deg, #f3e5f5, #ffffff); color: #222 !important; float: left; clear: both; } .error-bubble { background: #ffebee; color: #b71c1c !important; font-weight: bold; float: left; clear: both; } /* Sidebar styling */ section[data-testid="stSidebar"] { background: #e3f2fd !important; border-right: 2px solid #1976d2; } /* Sidebar text should be dark */ section[data-testid="stSidebar"] * { color: #1f2937 !important; } .sidebar-content { padding: 1rem; } /* Input fields in light mode */ .stTextInput > div > div > input { background-color: #ffffff !important; color: #1f2937 !important; border: 1px solid #d1d5db !important; } /* Select boxes in light mode */ .stSelectbox > div > div > select { background-color: #ffffff !important; color: #1f2937 !important; border: 1px solid #d1d5db !important; } /* Dropdown options in light mode */ .stSelectbox > div > div > select > option { background-color: #ffffff !important; color: #1f2937 !important; } /* Buttons in light mode - Strong selectors for all buttons */ .stButton > button { background: linear-gradient(45deg, #667eea, #764ba2) !important; color: #ffffff !important; border: none !important; font-weight: 600 !important; border-radius: 8px !important; padding: 0.6rem 1.2rem !important; min-height: 2.8rem !important; font-size: 14px !important; transition: all 0.2s ease !important; } .stButton > button:hover { background: linear-gradient(45deg, #5a67d8, #6b46c1) !important; color: #ffffff !important; transform: translateY(-1px) !important; } /* Ensure button text stays white - stronger selectors */ .stButton > button span, .stButton > button div, .stButton > button *, .stButton > button p { color: #ffffff !important; font-weight: 600 !important; } /* Specific targeting for all button states and text elements */ button[kind="primary"], button[kind="secondary"], div[data-testid="stButton"] > button, .stButton button { background: linear-gradient(45deg, #667eea, #764ba2) !important; color: #ffffff !important; border: none !important; font-weight: 600 !important; } /* All text inside buttons must be white */ button[kind="primary"] *, button[kind="primary"] span, button[kind="primary"] div, button[kind="primary"] p, button[kind="secondary"] *, button[kind="secondary"] span, button[kind="secondary"] div, button[kind="secondary"] p, div[data-testid="stButton"] > button *, div[data-testid="stButton"] > button span, div[data-testid="stButton"] > button div, div[data-testid="stButton"] > button p, .stButton button *, .stButton button span, .stButton button div, .stButton button p { color: #ffffff !important; font-weight: 600 !important; } button[kind="primary"]:hover, button[kind="secondary"]:hover, div[data-testid="stButton"] > button:hover, .stButton button:hover { background: linear-gradient(45deg, #5a67d8, #6b46c1) !important; color: #ffffff !important; } /* Hover state text colors */ button[kind="primary"]:hover *, button[kind="secondary"]:hover *, div[data-testid="stButton"] > button:hover *, .stButton button:hover * { color: #ffffff !important; } /* Force all button text elements to be white */ .stButton > button * { color: #ffffff !important; } /* Disabled button styling */ .stButton > button:disabled { background: #9ca3af !important; color: #ffffff !important; opacity: 0.6 !important; } .stButton > button:disabled * { color: #ffffff !important; } /* Copy button styling in light mode */ .copy-button { background: #6b7280 !important; color: #ffffff !important; border: none !important; } .copy-button:hover { background: #4b5563 !important; color: #ffffff !important; } /* Labels and form elements */ .stTextInput label, .stSelectbox label, .stRadio label, .stCheckbox label { color: #1f2937 !important; font-weight: 500 !important; } /* Metrics in light mode */ [data-testid="metric-container"] { background: #ffffff !important; border: 1px solid #e5e7eb !important; color: #1f2937 !important; } [data-testid="metric-container"] * { color: #1f2937 !important; } /* Additional button overrides for light theme */ div[data-testid="column"] .stButton > button { background: linear-gradient(45deg, #667eea, #764ba2) !important; color: #ffffff !important; } /* Force override of any inherited text colors */ .stButton > button p, .stButton > button div, .stButton > button span { color: #ffffff !important; } /* Hide Streamlit toolbar and menu */ .stToolbar { display: none !important; } /* Hide Streamlit header menu */ header[data-testid="stHeader"] { display: none !important; } /* Hide the running/rerun indicator */ .stAppView > .main .block-container { padding-top: 1rem !important; } """ def _get_dark_theme(self) -> str: """Returns CSS for dark theme.""" return """ /* Modern Dark Theme - High Contrast & Clean */ body, .stApp { background: #1a1a1a !important; color: #ffffff !important; } /* Override all text colors for visibility */ div, p, span, label, h1, h2, h3, h4, h5, h6 { color: #ffffff !important; } /* Professional header colors for dark theme */ .main-header h1 { color: #ffffff !important; } .main-header p { color: #9ca3af !important; } /* Main container */ .chat-container { max-width: 900px; margin: auto; } /* User message bubble - Clean Blue */ .user-bubble { background: #2563eb !important; color: #ffffff !important; float: right; clear: both; border: none !important; box-shadow: 0 2px 10px rgba(37, 99, 235, 0.4) !important; } /* AI message bubble - Clean Dark Gray */ .ai-bubble { background: #374151 !important; color: #ffffff !important; float: left; clear: both; border: 1px solid #4b5563 !important; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important; } /* Error bubble - Clean Red */ .error-bubble { background: #dc2626 !important; color: #ffffff !important; font-weight: bold; float: left; clear: both; border: none !important; box-shadow: 0 2px 10px rgba(220, 38, 38, 0.4) !important; } /* Sidebar - Clean Dark */ section[data-testid="stSidebar"] { background: #2d2d2d !important; border-right: 2px solid #4b5563 !important; } /* Sidebar content visibility */ section[data-testid="stSidebar"] * { color: #ffffff !important; } /* Input fields - High contrast */ .stTextInput > div > div > input { background-color: #374151 !important; color: #ffffff !important; border: 2px solid #6b7280 !important; } /* Select boxes - High contrast */ .stSelectbox > div > div > select { background-color: #374151 !important; color: #ffffff !important; border: 2px solid #6b7280 !important; } /* Dropdown options styling */ .stSelectbox > div > div > select > option { background-color: #374151 !important; color: #ffffff !important; } /* Alternative dropdown styling for better browser support */ div[data-baseweb="select"] { background-color: #374151 !important; } div[data-baseweb="select"] > div { background-color: #374151 !important; color: #ffffff !important; border: 2px solid #6b7280 !important; } /* Dropdown menu styling */ ul[role="listbox"] { background-color: #374151 !important; border: 1px solid #6b7280 !important; } li[role="option"] { background-color: #374151 !important; color: #ffffff !important; } li[role="option"]:hover { background-color: #4b5563 !important; color: #ffffff !important; } /* Buttons - Clean and visible - Strong selectors for all buttons */ .stButton > button { background: #2563eb !important; color: #ffffff !important; border: none !important; font-weight: 600 !important; border-radius: 8px !important; padding: 0.6rem 1.2rem !important; min-height: 2.8rem !important; font-size: 14px !important; transition: all 0.2s ease !important; } .stButton > button:hover { background: #1d4ed8 !important; color: #ffffff !important; transform: translateY(-1px) !important; } /* Ensure button text stays white - stronger selectors */ .stButton > button span, .stButton > button div, .stButton > button *, .stButton > button p { color: #ffffff !important; font-weight: 600 !important; } /* Specific targeting for all button states and text elements */ button[kind="primary"], button[kind="secondary"], div[data-testid="stButton"] > button, .stButton button { background: #2563eb !important; color: #ffffff !important; border: none !important; font-weight: 600 !important; } /* All text inside buttons must be white */ button[kind="primary"] *, button[kind="primary"] span, button[kind="primary"] div, button[kind="primary"] p, button[kind="secondary"] *, button[kind="secondary"] span, button[kind="secondary"] div, button[kind="secondary"] p, div[data-testid="stButton"] > button *, div[data-testid="stButton"] > button span, div[data-testid="stButton"] > button div, div[data-testid="stButton"] > button p, .stButton button *, .stButton button span, .stButton button div, .stButton button p { color: #ffffff !important; font-weight: 600 !important; } button[kind="primary"]:hover, button[kind="secondary"]:hover, div[data-testid="stButton"] > button:hover, .stButton button:hover { background: #1d4ed8 !important; color: #ffffff !important; } /* Hover state text colors */ button[kind="primary"]:hover *, button[kind="secondary"]:hover *, div[data-testid="stButton"] > button:hover *, .stButton button:hover * { color: #ffffff !important; } /* Expander - Clean styling */ .streamlit-expanderHeader { background-color: #374151 !important; color: #ffffff !important; border: 1px solid #6b7280 !important; } /* Metrics - High visibility */ [data-testid="metric-container"] { background: #374151 !important; border: 1px solid #6b7280 !important; color: #ffffff !important; } [data-testid="metric-container"] * { color: #ffffff !important; } /* Code blocks - GitHub dark style */ pre, code { background-color: #0d1117 !important; color: #f0f6fc !important; border: 1px solid #30363d !important; } /* Tables - Clean dark */ .dataframe, .dataframe * { background-color: #374151 !important; color: #ffffff !important; } /* Table headers */ .dataframe th { background-color: #2563eb !important; color: #ffffff !important; } /* Streamlit widgets override */ .stRadio > div, .stCheckbox > div { color: #ffffff !important; } .stRadio > div > label, .stCheckbox > div > label { color: #ffffff !important; } /* Spinner for dark theme */ .stSpinner > div { border-top-color: #2563eb !important; } /* Success/Info messages */ .stSuccess, .stInfo { background-color: #374151 !important; color: #ffffff !important; border: 1px solid #6b7280 !important; } /* Warning messages */ .stWarning { background-color: #f59e0b !important; color: #000000 !important; } /* Error messages */ .stError { background-color: #dc2626 !important; color: #ffffff !important; } /* Additional dropdown fixes */ .stSelectbox [data-testid="stMarkdownContainer"] { color: #ffffff !important; } /* Streamlit's custom selectbox */ div[data-testid="stSelectbox"] div[data-testid="stMarkdownContainer"] p { color: #ffffff !important; } /* Multi-select components */ .stMultiSelect > div > div > div { background-color: #374151 !important; color: #ffffff !important; border: 2px solid #6b7280 !important; } /* Radio button labels */ .stRadio div[role="radiogroup"] label { color: #ffffff !important; } /* Hide Streamlit toolbar and menu */ .stToolbar { display: none !important; } /* Hide Streamlit header menu */ header[data-testid="stHeader"] { display: none !important; } /* Hide the running/rerun indicator */ .stAppView > .main .block-container { padding-top: 1rem !important; } """ def _get_custom_theme(self) -> str: """Returns CSS for custom theme.""" return """ body, .stApp { background: #fffbe7 !important; } .chat-container { max-width: 900px; margin: auto; } .user-bubble { background: linear-gradient(90deg, #ffe082, #fffbe7); color: #6d4c00; float: right; clear: both; } .ai-bubble { background: linear-gradient(90deg, #b2dfdb, #fffbe7); color: #004d40; float: left; clear: both; } .error-bubble { background: #ffe0b2; color: #b71c1c; font-weight: bold; float: left; clear: both; } section[data-testid="stSidebar"] { background: #ffe082 !important; } /* Hide Streamlit toolbar and menu */ .stToolbar { display: none !important; } /* Hide Streamlit header menu */ header[data-testid="stHeader"] { display: none !important; } /* Hide the running/rerun indicator */ .stAppView > .main .block-container { padding-top: 1rem !important; } """ def _get_common_styles(self) -> str: """Returns common CSS styles for all themes.""" return """ /* Professional header styling */ .main-header h1 { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif !important; font-weight: 600 !important; line-height: 1.2 !important; margin: 0 !important; } .main-header p { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif !important; font-weight: 400 !important; line-height: 1.4 !important; margin: 0 !important; } .user-bubble, .ai-bubble, .error-bubble { border-radius: 14px; padding: 10px 14px; margin: 6px 0; display: inline-block; max-width: 75%; word-wrap: break-word; box-shadow: 0 2px 8px rgba(0,0,0,0.25); } .spinner { display: inline-block; width: 1.3em; height: 1.3em; border: 3px solid #e0e0e0; border-top: 3px solid #2193b0; border-radius: 50%; animation: spin 0.8s linear infinite; margin-right: 0.7em; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Enhanced button styling for all themes */ .stButton > button { border-radius: 8px !important; font-weight: 600 !important; transition: all 0.2s ease !important; padding: 0.6rem 1.2rem !important; min-height: 2.8rem !important; font-size: 14px !important; border: none !important; } .stButton > button:hover { transform: translateY(-1px) !important; } /* Force all button text elements to maintain proper colors - Super strong selectors */ .stButton > button *, .stButton > button p, .stButton > button div, .stButton > button span, .stButton > button strong, .stButton > button em, .stButton > button i, .stButton > button b, div[data-testid="stButton"] > button *, div[data-testid="stButton"] > button p, div[data-testid="stButton"] > button div, div[data-testid="stButton"] > button span, div[data-testid="stButton"] > button strong, div[data-testid="stButton"] > button em, div[data-testid="stButton"] > button i, div[data-testid="stButton"] > button b, button[kind="primary"] *, button[kind="primary"] p, button[kind="primary"] div, button[kind="primary"] span, button[kind="primary"] strong, button[kind="primary"] em, button[kind="primary"] i, button[kind="primary"] b, button[kind="secondary"] *, button[kind="secondary"] p, button[kind="secondary"] div, button[kind="secondary"] span, button[kind="secondary"] strong, button[kind="secondary"] em, button[kind="secondary"] i, button[kind="secondary"] b, .stButton button *, .stButton button p, .stButton button div, .stButton button span, .stButton button strong, .stButton button em, .stButton button i, .stButton button b { font-weight: 600 !important; /* Note: color is set by individual themes */ } /* Additional strong selectors for buttons */ div[data-testid="stButton"] button, .stButton button, button[data-testid], [data-testid="stButton"] button { border-radius: 8px !important; font-weight: 600 !important; transition: all 0.2s ease !important; padding: 0.6rem 1.2rem !important; min-height: 2.8rem !important; font-size: 14px !important; border: none !important; } /* Ensure ALL button content inherits button color */ .stButton > button, div[data-testid="stButton"] > button, button[kind="primary"], button[kind="secondary"], .stButton button { /* Background and text colors are set by individual themes */ } /* Critical: Force text color inheritance from button */ .stButton > button *, div[data-testid="stButton"] > button *, button[kind="primary"] *, button[kind="secondary"] *, .stButton button * { color: inherit !important; } /* Scrollbar styling */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: rgba(0,0,0,0.1); border-radius: 4px; } ::-webkit-scrollbar-thumb { background: rgba(128,128,128,0.4); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: rgba(128,128,128,0.6); } """ def inject_theme(self, theme_name: str) -> None: """Inject the selected theme CSS into the Streamlit app.""" if theme_name not in self.themes: theme_name = "Light" # Default fallback theme_css = self.themes[theme_name] common_css = self._get_common_styles() combined_css = theme_css + common_css st.markdown(f"", unsafe_allow_html=True)