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