AI-Course-Research / src /streamlit_app.py
feministmystique's picture
Update src/streamlit_app.py
bdf2c15 verified
import os
import pathlib
os.environ["HOME"] = "/tmp"
cfg_dir = pathlib.Path("/tmp/.streamlit")
cfg_dir.mkdir(parents=True, exist_ok=True)
(cfg_dir / "config.toml").write_text(
"[server]\nheadless = true\nport = 7860\naddress = \"0.0.0.0\"\n\n[browser]\ngatherUsageStats = false\n",
encoding="utf-8",
)
import time
import streamlit as st
from huggingface_hub import InferenceClient
import re
import gspread
from google.oauth2.service_account import Credentials
from datetime import datetime
import json
import logging
from typing import Optional, Dict, Any
import uuid
from huggingface_hub.utils import HfHubHTTPError
import httpx
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
st.set_page_config(
page_title="Interactive Help Interface",
layout="centered",
initial_sidebar_state="collapsed"
)
DEFAULT_QUESTIONS = [
"Please familiarize yourself with all the buttons before completing the questions below.",
"What is sin(Ο€/6)?",
"What is the center and radius of the circle indicated by the equation?\n(x-2)^2 + y^2 = 36",
"A conic section is represented by the following equation:\n-4x^2 + 15y^2+343=0\nWhat type of conic section does this equation represent?",
"Find the vertex (x,y) for a parabola with equation:\ny=3x^2 -6x + 1",
"What are the solutions to y = x^2 + x - 6?"
]
MODEL = "deepseek-ai/DeepSeek-R1-0528"
SUBJECT = "Precalculus"
WORD_LIMIT = 180
def _load_questions_json(path: str):
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
subj = data.get("Subject")
qs = data.get("Questions")
if isinstance(subj, str) and isinstance(qs, list) and len(qs) > 0 and all(isinstance(x, str) for x in qs):
return subj, qs
except Exception as e:
logger.error(f"Error loading questions JSON: {e}")
return None, None
QUESTIONS_JSON_PATH = os.getenv("QUESTIONS_JSON", "src/questions.json")
_loaded_subject, _loaded_questions = _load_questions_json(QUESTIONS_JSON_PATH)
if _loaded_subject and _loaded_questions:
SUBJECT = _loaded_subject
DEFAULT_QUESTIONS = _loaded_questions
class ConfigManager:
def __init__(self):
self.hf_token = os.getenv("HF_TOKEN")
self.google_creds_json = os.getenv("GOOGLE_SHEETS_CREDENTIALS")
self.google_sheets_id = os.getenv("GOOGLE_SHEETS_ID")
missing_vars = []
def get_google_client(self):
if not self.google_creds_json:
st.info("Google Sheets integration is disabled (no credentials).")
return None
try:
creds_dict = json.loads(self.google_creds_json)
creds = Credentials.from_service_account_info(creds_dict, scopes=[
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive"
])
return gspread.authorize(creds)
except Exception as e:
st.error(f"Error creating Google Sheets client: {e}")
return None
class GoogleSheetsLogger:
"""Handles Google Sheets logging functionality"""
def __init__(self, config: ConfigManager):
self.config = config
@st.cache_resource(show_spinner=False)
def _init_google_sheets(creds_json: str, sheet_id: str):
"""Initialize Google Sheets connection with caching based on inputs."""
try:
if not creds_json or not sheet_id:
logger.info("Google Sheets disabled: missing creds_json or sheet_id.")
return None
creds_dict = json.loads(creds_json)
scope = [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive"
]
creds = Credentials.from_service_account_info(creds_dict, scopes=scope)
client = gspread.authorize(creds)
spreadsheet = client.open_by_key(sheet_id)
sheet = spreadsheet.sheet1
headers = sheet.row_values(1)
if not headers:
sheet.append_row([
"Timestamp", "Session_ID", "Button_Clicked",
"Question", "Subject", "Help_Click_Count", "Retry_Count"
])
return sheet
except Exception as e:
logger.error(f"Error connecting to Google Sheets: {e}")
return None
def log_button_click(self, button_name, question, subject, help_clicks, retry_count):
try:
sheet = _get_sheet(self.config.google_creds_json, self.config.google_sheets_id)
if not sheet:
logger.info("Google Sheets not available; skipping log.")
return
if "session_id" not in st.session_state:
st.session_state.session_id = str(uuid.uuid4())
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sheet.append_row([
timestamp,
st.session_state.session_id,
button_name,
question,
subject,
help_clicks,
retry_count
])
except Exception as e:
logger.error(f"Error logging to Google Sheets: {e}")
class AIAssistant:
"""Handles AI model interactions"""
def __init__(self, config: ConfigManager):
self.model = MODEL
self.client = InferenceClient(
model=self.model,
token=os.getenv("HF_TOKEN") or config.hf_token,
timeout=60.0,
)
self.base_prompt = f"""
You are an AI assistant designed to support high school students in the subject of {SUBJECT}.
Your role is to offer friendly, helpful, concise, in-depth guidance, just like a supportive teacher would.
Please follow these guidelines:
1. Maintain a polite, respectful, and professional tone at all times.
2. Adhere to ethical principles β€” do not promote cheating, harmful behavior, or misinformation.
3. Interact in a warm, encouraging, and student-centered style β€” use clear explanations, positive reinforcement, and examples when needed.
4. The word limit is {WORD_LIMIT} words.
5. Do not give the direct answer to the question.
"""
self.prompt_templates = {
"Explain the question": """
6. Focus on thoroughly explaining the question by breaking down its components. Clarify the key concepts and definitions involved, ensuring that the explanation helps the reader fully understand what the question is asking. Avoid jumping to answers or examples; instead, concentrate on making the meaning and scope of the question clear.
7. Do not include specific examples or real-world applications in your response.
""",
"Give an example": """
6. Focus on providing three distinct, simple examples of this general question. Each example should highlight a unique approach or scenario related to the topic, helping to clarify the concept from multiple perspectives.
7. Do not include any explanation or real-world applications.
8. Do not give an example with the same question provided.
""",
"Who cares?": """
6. Explain two reasons why learning this subject in school is useful. Perhaps provide real-world applications.
7. Do not include any explanation or examples about the specific question provided.
"""
}
def generate_response(self, button_name: str, question: str, retry_count: int = 0) -> str:
try:
system_text = self.base_prompt + self.prompt_templates.get(button_name, "")
if retry_count > 0:
system_text += f"\nPlease provide a different explanation. This is attempt {retry_count + 1}."
user_text = f"Question:\n{question}"
full_prompt = f"{system_text}\n\n{user_text}"
try:
text = self.client.text_generation(
prompt=full_prompt,
max_new_tokens=300,
temperature=0.7,
repetition_penalty=1.1,
model=self.model,
)
except (HfHubHTTPError, ValueError) as e:
msg = str(e)
unsupported = (
"Task 'text-generation' not supported" in msg
or "doesn't support task 'text-generation'" in msg
or "Available tasks: ['conversational']" in msg
)
if unsupported:
messages = [
{"role": "system", "content": system_text},
{"role": "user", "content": user_text},
]
chat = self.client.chat_completion(
messages=messages,
max_tokens=350,
temperature=0.7,
model=self.model,
)
text = ""
try:
choices = getattr(chat, "choices", None) or chat.get("choices", [])
if choices:
msg0 = choices[0].get("message") or {}
text = msg0.get("content") or ""
if not text:
text = getattr(chat, "generated_text", None) or chat.get("generated_text", "") or ""
except Exception:
text = str(chat)
else:
raise
except (httpx.ReadTimeout, httpx.ConnectTimeout):
return "The model request timed out. Please try again."
cleaned = re.sub(r"<think>.*?</think>", "", text or "", flags=re.DOTALL)
return cleaned.strip() or "Sorry, I couldn't generate a response."
except Exception as e:
logger.exception(f"Error generating AI response: {e}")
return "Sorry, I encountered an error generating a response. Please try again."
class SessionManager:
"""Manages Streamlit session state"""
@staticmethod
def initialize_session_state():
defaults = {
"responses": {},
"retry_counts": {},
"help_clicks": {},
"button_clicked": {},
"show_sections": {},
"help_dismissed": {},
"loading_states": {},
"submitted": {},
"session_id": str(uuid.uuid4())
}
for key, default in defaults.items():
if key not in st.session_state:
st.session_state[key] = default
@staticmethod
def get_question_state(question_id: str, key: str, default=None):
if key not in st.session_state:
st.session_state[key] = {}
return st.session_state[key].get(question_id, default)
@staticmethod
def set_question_state(question_id: str, key: str, value):
if key not in st.session_state:
st.session_state[key] = {}
st.session_state[key][question_id] = value
class HelpInterface:
"""Main help interface component with demo support"""
def __init__(self, config: ConfigManager):
self.config = config
self.logger = GoogleSheetsLogger(config)
self.ai_assistant = AIAssistant(config)
self.session_manager = SessionManager()
self.session_manager.initialize_session_state()
def render_question(self, question: str, question_id: str, is_demo: bool = False):
logger.info(f"Rendering question {question_id}, is_demo: {is_demo}")
if is_demo:
st.markdown(f"""
<div style='
background: linear-gradient(135deg, #fdcb6e 0%, #e17055 100%);
padding: 25px;
border-radius: 15px;
margin: 25px 0;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
border: 2px solid #e0e4e7;
'>
<h2 style='
color: white;
margin: 0 0 15px 0;
font-size: 1.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
'>🎯 Demo</h2>
<div style='
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: inset 0 2px 10px rgba(0,0,0,0.1);
'>
<p style='
font-size: 18px;
line-height: 1.6;
margin: 0;
color: #2c3e50;
font-weight: 500;
'>{question}</p>
</div>
</div>
""", unsafe_allow_html=True)
else:
st.markdown(f"""
<div style='
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 25px;
border-radius: 15px;
margin: 25px 0;
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
border: 2px solid #e0e4e7;
'>
<h2 style='
color: white;
margin: 0 0 15px 0;
font-size: 1.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
'>πŸ“š Question {question_id}</h2>
<div style='
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: inset 0 2px 10px rgba(0,0,0,0.1);
'>
<p style='
font-size: 18px;
line-height: 1.6;
margin: 0;
color: #2c3e50;
font-weight: 500;
'>{question}</p>
</div>
</div>
""", unsafe_allow_html=True)
is_submitted = self.session_manager.get_question_state(question_id, "submitted", False)
if not is_submitted:
st.markdown("<div style='margin: 20px 0;'>", unsafe_allow_html=True)
st.text_area(
"✏️ Your Response:",
value="",
height=100,
key=f"response_{question_id}",
placeholder="Write your answer here..."
)
st.markdown("</div>", unsafe_allow_html=True)
st.markdown("""
<div style='
margin: 30px 0 20px 0;
padding: 0px;
background: transparent;
'>
""", unsafe_allow_html=True)
col1, col2, col3 = st.columns([1, 0.2, 1])
with col1:
if not is_submitted:
help_button_style = """
<style>
div[data-testid="column"] > div > div > div > button[kind="primary"] {
width: 100%;
height: 55px;
font-size: 16px;
font-weight: 600;
border-radius: 12px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: white;
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
cursor: pointer;
}
div[data-testid="column"] > div > div > div > button[kind="primary"]:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(102, 126, 234, 0.4);
background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%);
}
</style>
"""
st.markdown(help_button_style, unsafe_allow_html=True)
if st.button("πŸ” Need Help?", key=f"help_{question_id}", type="primary", use_container_width=True):
self._toggle_help(question, question_id, is_demo)
else:
st.markdown("<div style='height: 55px;'></div>", unsafe_allow_html=True)
with col2:
st.markdown("<div style='height: 55px;'></div>", unsafe_allow_html=True)
with col3:
if not is_submitted:
submit_button_style = """
<style>
div[data-testid="column"]:nth-child(3) > div > div > div > button {
width: 100%;
height: 55px;
font-size: 16px;
font-weight: 600;
border-radius: 12px;
background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
border: none;
color: white;
box-shadow: 0 4px 20px rgba(72, 187, 120, 0.3);
transition: all 0.3s ease;
cursor: pointer;
}
div[data-testid="column"]:nth-child(3) > div > div > div > button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 25px rgba(72, 187, 120, 0.4);
background: linear-gradient(135deg, #38a169 0%, #2f855a 100%);
}
</style>
"""
st.markdown(submit_button_style, unsafe_allow_html=True)
if st.button("πŸ“ Submit Answer", key=f"submit_{question_id}", use_container_width=True):
self._handle_submit(question, question_id, is_demo)
else:
st.markdown("""
<div style='
background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
color: white;
padding: 15px;
border-radius: 12px;
text-align: center;
font-weight: 600;
font-size: 16px;
box-shadow: 0 4px 20px rgba(72, 187, 120, 0.3);
border: none;
height: 55px;
display: flex;
align-items: center;
justify-content: center;
'>
βœ… Successfully Submitted!
</div>
""", unsafe_allow_html=True)
st.markdown("</div>", unsafe_allow_html=True)
if (self.session_manager.get_question_state(question_id, "show_sections", False) and
not is_submitted):
self._render_help_section(question, question_id, is_demo)
st.markdown("<div style='margin-bottom: 40px;'></div>", unsafe_allow_html=True)
def _handle_submit(self, question: str, question_id: str, is_demo: bool = False):
logger.info(f"Handle submit called for question {question_id}, is_demo: {is_demo}")
self.session_manager.set_question_state(question_id, "submitted", True)
if not is_demo:
help_clicks = self.session_manager.get_question_state(question_id, "help_clicks", 0)
retry_count = self.session_manager.get_question_state(question_id, "retry_counts", 0)
logger.info(f"Logging submit for question {question_id}")
self.logger.log_button_click(
"Submit", question, SUBJECT, help_clicks, retry_count
)
else:
logger.info(f"Skipping submit logging for demo question {question_id}")
st.rerun()
def _toggle_help(self, question: str, question_id: str, is_demo: bool = False):
logger.info(f"Toggle help called for question {question_id}, is_demo: {is_demo}")
current_help_clicks = self.session_manager.get_question_state(question_id, "help_clicks", 0)
self.session_manager.set_question_state(question_id, "help_clicks", current_help_clicks + 1)
current_show = self.session_manager.get_question_state(question_id, "show_sections", False)
self.session_manager.set_question_state(question_id, "show_sections", not current_show)
self.session_manager.set_question_state(question_id, "button_clicked", None)
self.session_manager.set_question_state(question_id, "help_dismissed", False)
if not is_demo:
retry_count = self.session_manager.get_question_state(question_id, "retry_counts", 0)
logger.info(f"Logging help click for question {question_id}")
self.logger.log_button_click(
"Help", question, SUBJECT,
current_help_clicks + 1, retry_count
)
else:
logger.info(f"Skipping logging for demo question {question_id}")
def _render_help_section(self, question: str, question_id: str, is_demo: bool = False):
st.markdown("""
<div style='
background: linear-gradient(135deg, #ffeaa7 0%, #fab1a0 100%);
padding: 15px;
border-radius: 10px;
margin: 15px 0;
border: 1px solid #e17055;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
'>
<h4 style='
margin: 0 0 10px 0;
color: #2d3436;
font-size: 1.1em;
'>🎯 Choose Your Learning Style</h4>
<p style='
margin: 0;
font-size: 14px;
color: #636e72;
'>Select the type of help that works best for you:</p>
</div>
""", unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
with col1:
if st.button("πŸ“ Explain", key=f"explain_{question_id}", use_container_width=True):
self._handle_help_button("Explain the question", question, question_id, is_demo)
with col2:
if st.button("πŸ’‘ Example", key=f"example_{question_id}", use_container_width=True):
self._handle_help_button("Give an example", question, question_id, is_demo)
with col3:
if st.button("πŸ€” Who cares?", key=f"application_{question_id}", use_container_width=True):
self._handle_help_button("Who cares?", question, question_id, is_demo)
current_response = self.session_manager.get_question_state(question_id, "responses", "")
current_button = self.session_manager.get_question_state(question_id, "button_clicked", None)
if current_response and current_button:
st.write(current_response)
self._render_action_buttons(question, question_id, is_demo)
def _handle_help_button(self, button_name: str, question: str, question_id: str, is_demo: bool = False):
logger.info(f"Handle help button called: {button_name} for question {question_id}, is_demo: {is_demo}")
if not is_demo:
help_clicks = self.session_manager.get_question_state(question_id, "help_clicks", 0)
retry_count = self.session_manager.get_question_state(question_id, "retry_counts", 0)
logger.info(f"Logging {button_name} click for question {question_id}")
self.logger.log_button_click(
button_name, question, SUBJECT, help_clicks, retry_count
)
else:
logger.info(f"Skipping logging for demo question {question_id}")
with st.spinner("Generating response..."):
retry_count = self.session_manager.get_question_state(question_id, "retry_counts", 0)
response = self.ai_assistant.generate_response(button_name, question, retry_count)
self.session_manager.set_question_state(question_id, "responses", response)
self.session_manager.set_question_state(question_id, "button_clicked", button_name)
self.session_manager.set_question_state(question_id, "retry_counts", 0)
st.rerun()
def _render_action_buttons(self, question: str, question_id: str, is_demo: bool = False):
st.markdown("<div style='margin-top: 15px;'>", unsafe_allow_html=True)
col1, col2, col3 = st.columns(3)
with col1:
if st.button("πŸ”„ Retry", key=f"retry_{question_id}", use_container_width=True):
self._handle_retry(question, question_id, is_demo)
with col2:
if st.button("πŸ’¬ ChatGPT", key=f"chatgpt_{question_id}", use_container_width=True):
self._handle_chatgpt_redirect(question, question_id, is_demo)
with col3:
if st.button("βœ… Helped!", key=f"thanks_{question_id}", use_container_width=True):
self._handle_thank_you(question, question_id, is_demo)
st.markdown("</div>", unsafe_allow_html=True)
def _handle_retry(self, question: str, question_id: str, is_demo: bool = False):
logger.info(f"Handle retry called for question {question_id}, is_demo: {is_demo}")
current_retry = self.session_manager.get_question_state(question_id, "retry_counts", 0)
new_retry_count = current_retry + 1
self.session_manager.set_question_state(question_id, "retry_counts", new_retry_count)
if not is_demo:
help_clicks = self.session_manager.get_question_state(question_id, "help_clicks", 0)
logger.info(f"Logging retry for question {question_id}")
self.logger.log_button_click(
"More help", question, SUBJECT, help_clicks, new_retry_count
)
else:
logger.info(f"Skipping retry logging for demo question {question_id}")
current_button = self.session_manager.get_question_state(question_id, "button_clicked", None)
if current_button:
with st.spinner("Generating alternative response..."):
response = self.ai_assistant.generate_response(current_button, question, new_retry_count)
self.session_manager.set_question_state(question_id, "responses", response)
st.rerun()
def _handle_chatgpt_redirect(self, question: str, question_id: str, is_demo: bool = False):
logger.info(f"Handle ChatGPT redirect called for question {question_id}, is_demo: {is_demo}")
if not is_demo:
help_clicks = self.session_manager.get_question_state(question_id, "help_clicks", 0)
retry_count = self.session_manager.get_question_state(question_id, "retry_counts", 0)
logger.info(f"Logging ChatGPT redirect for question {question_id}")
self.logger.log_button_click(
"Take me to ChatGPT", question, SUBJECT, help_clicks, retry_count
)
else:
logger.info(f"Skipping ChatGPT redirect logging for demo question {question_id}")
st.markdown(f"""
<div style='text-align: center; margin: 15px 0;'>
<a href="https://chat.openai.com" target="_blank" style="
display: inline-block;
background: linear-gradient(135deg, #10A37F 0%, #0e8c6b 100%);
color: white;
padding: 12px 24px;
text-decoration: none;
border-radius: 8px;
font-weight: 500;
box-shadow: 0 4px 15px rgba(16, 163, 127, 0.3);
transition: all 0.3s ease;
">🌐 Open ChatGPT</a>
</div>
""", unsafe_allow_html=True)
def _handle_thank_you(self, question: str, question_id: str, is_demo: bool = False):
logger.info(f"Handle thank you called for question {question_id}, is_demo: {is_demo}")
if not is_demo:
help_clicks = self.session_manager.get_question_state(question_id, "help_clicks", 0)
retry_count = self.session_manager.get_question_state(question_id, "retry_counts", 0)
logger.info(f"Logging thank you for question {question_id}")
self.logger.log_button_click(
"Thank you", question, SUBJECT, help_clicks, retry_count
)
else:
logger.info(f"Skipping thank you logging for demo question {question_id}")
self.session_manager.set_question_state(question_id, "show_sections", False)
self.session_manager.set_question_state(question_id, "help_dismissed", True)
st.success("πŸŽ‰ Glad I could help! Feel free to ask for help anytime.")
st.rerun()
@st.cache_resource(show_spinner=False)
def _get_sheet(_creds_json: str, _sheet_id: str):
try:
if not _creds_json or not _sheet_id:
logger.info("Sheets disabled: missing creds or id.")
return None
creds_dict = json.loads(_creds_json)
scope = [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive",
]
creds = Credentials.from_service_account_info(creds_dict, scopes=scope)
client = gspread.authorize(creds)
spreadsheet = client.open_by_key(_sheet_id)
sheet = spreadsheet.sheet1
headers = sheet.row_values(1)
if not headers:
sheet.append_row([
"Timestamp","Session_ID","Button_Clicked",
"Question","Subject","Help_Click_Count","Retry_Count"
])
return sheet
except Exception as e:
logger.error(f"Error connecting to Google Sheets: {e}")
return None
def main():
st.markdown("""
<div style='
text-align: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 30px;
border-radius: 20px;
margin-bottom: 30px;
box-shadow: 0 10px 40px rgba(0,0,0,0.15);
'>
<h1 style='
color: white;
margin: 0 0 10px 0;
font-size: 2.5em;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
'>πŸŽ“ Interactive Learning Quiz</h1>
<p style='
color: #e8eaed;
margin: 0;
font-size: 1.2em;
font-weight: 300;
'>Subject: {SUBJECT}</p>
</div>
""".format(SUBJECT=SUBJECT), unsafe_allow_html=True)
config = ConfigManager()
interface = HelpInterface(config)
question_number = 1
for i, question in enumerate(DEFAULT_QUESTIONS):
if i == 0:
interface.render_question(question, "demo", is_demo=True)
else:
interface.render_question(question, str(question_number), is_demo=False)
question_number += 1
st.markdown("""
<style>
.stButton > button {
height: 2.5em;
font-size: 0.9rem;
border-radius: 8px;
transition: all 0.3s ease;
border: none;
font-weight: 500.
}
.stButton > button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0,0,0,0.15);
}
.stButton > button[kind="primary"] {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.stTextArea > div > div > textarea {
border-radius: 10px;
border: 2px solid #e0e4e7;
font-size: 16px;
padding: 12px;
transition: all 0.3s ease;
}
.stTextArea > label {
font-size: 14px;
font-weight: 500;
color: #666;
}
.stTextArea > div > div > textarea:focus {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.block-container {
padding-top: 2rem;
padding-bottom: 2rem;
}
.stMarkdown {
margin-bottom: 0;
}
.stSuccess {
background: linear-gradient(135deg, #00b894 0%, #00a085 100%);
border: none;
border-radius: 10px;
padding: 15px;
color: white;
}
.stSpinner {
color: #667eea;
}
</style>
""", unsafe_allow_html=True)
if __name__ == "__main__":
main()