Spaces:
Sleeping
Sleeping
| """ | |
| 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"<style>{combined_css}</style>", unsafe_allow_html=True) | |