"""
app.py — Streamlit front-end for the Groq chatbot.
Run locally: streamlit run app.py
Deploy: push to GitHub → connect at share.streamlit.io
"""
import json
import streamlit as st
from chatbot import Chatbot, AVAILABLE_MODELS, DEFAULT_SYSTEM_PROMPT
# ── Page config (must be first Streamlit call) ────────────────────────────────
st.set_page_config(
page_title="CampusMind AI",
page_icon="🎓",
layout="wide",
initial_sidebar_state="expanded",
)
# ── Custom CSS ────────────────────────────────────────────────────────────────
st.markdown("""
""", unsafe_allow_html=True)
# ── Session state init ────────────────────────────────────────────────────────
def init_state():
if "messages" not in st.session_state:
st.session_state.messages = [] # [{role, content}]
if "bot" not in st.session_state:
st.session_state.bot = None
if "api_key_set" not in st.session_state:
st.session_state.api_key_set = False
if "system_prompt" not in st.session_state:
st.session_state.system_prompt = DEFAULT_SYSTEM_PROMPT
if "selected_model" not in st.session_state:
st.session_state.selected_model = list(AVAILABLE_MODELS.keys())[0]
init_state()
# ── Sidebar ───────────────────────────────────────────────────────────────────
with st.sidebar:
st.markdown('
', unsafe_allow_html=True)
# API key input
st.markdown('', unsafe_allow_html=True)
# Check Streamlit secrets first (for deployment), then env var
import os
env_key = os.environ.get("GROQ_API_KEY", "")
secret_key = ""
try:
secret_key = st.secrets.get("GROQ_API_KEY", "")
except Exception:
pass
prefilled = env_key or secret_key
api_key_input = st.text_input(
"Groq API Key",
value=prefilled,
type="password",
placeholder="gsk_...",
label_visibility="collapsed",
help="Get your free key at console.groq.com",
)
if api_key_input:
if not st.session_state.api_key_set or st.session_state.bot is None:
try:
st.session_state.bot = Chatbot(
api_key=api_key_input,
system_prompt=st.session_state.system_prompt,
model=st.session_state.selected_model,
)
st.session_state.api_key_set = True
except Exception as e:
st.error(f"Failed to init: {e}")
st.success("✓ Connected", icon="⚡")
else:
st.info("Enter your Groq API key to start. Free at [console.groq.com](https://console.groq.com)")
# Model selector
st.markdown('', unsafe_allow_html=True)
model_label = st.selectbox(
"Model",
options=list(AVAILABLE_MODELS.values()),
index=0,
label_visibility="collapsed",
)
# Map label back to model ID
selected_model_id = [k for k, v in AVAILABLE_MODELS.items() if v == model_label][0]
if st.session_state.bot and selected_model_id != st.session_state.selected_model:
st.session_state.selected_model = selected_model_id
st.session_state.bot.change_model(selected_model_id)
# System prompt
st.markdown('', unsafe_allow_html=True)
new_prompt = st.text_area(
"System prompt",
value=st.session_state.system_prompt,
height=130,
label_visibility="collapsed",
placeholder="You are a helpful assistant...",
)
if new_prompt != st.session_state.system_prompt:
st.session_state.system_prompt = new_prompt
if st.session_state.bot:
st.session_state.bot.change_system_prompt(new_prompt)
# Quick personas
st.markdown('', unsafe_allow_html=True)
personas = {
"🤖 Default Assistant": DEFAULT_SYSTEM_PROMPT,
"🐍 Python Tutor": "You are an expert Python tutor. Only answer Python/coding questions. Give short code examples with every answer.",
"✍️ Writing Coach": "You are a professional writing coach. Help improve clarity, grammar, and style. Be encouraging but direct.",
"📊 Data Analyst": "You are a data analyst. Help with data, statistics, SQL, and visualization. Use concrete examples.",
"🏴☠️ Pirate": "You are a swashbuckling pirate. Answer every question in pirate speak. Arrr!",
}
for label, prompt in personas.items():
if st.button(label, key=f"persona_{label}"):
st.session_state.system_prompt = prompt
if st.session_state.bot:
st.session_state.bot.change_system_prompt(prompt)
st.rerun()
# Controls
st.markdown('', unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
if st.button("🗑 Clear chat"):
st.session_state.messages = []
if st.session_state.bot:
st.session_state.bot.reset()
st.rerun()
with col2:
if st.button("💾 Export"):
if st.session_state.messages:
export = json.dumps(st.session_state.messages, indent=2)
st.download_button(
"⬇ Download JSON",
data=export,
file_name="conversation.json",
mime="application/json",
)
else:
st.warning("No messages to export.")
# Stats
if st.session_state.messages:
turns = len(st.session_state.messages) // 2
words = sum(len(m["content"].split()) for m in st.session_state.messages)
st.markdown(
f''
f'
turns {turns}
'
f'
words {words}
'
f'
',
unsafe_allow_html=True,
)
# ── Main chat area ────────────────────────────────────────────────────────────
st.markdown(
'',
unsafe_allow_html=True,
)
# Message display
chat_container = st.container()
with chat_container:
if not st.session_state.messages:
st.markdown("""
🎓
Welcome to CampusMind AI
Blazing fast inference — free tier available.
Enter your API key in the sidebar to begin.
""", unsafe_allow_html=True)
else:
for msg in st.session_state.messages:
is_user = msg["role"] == "user"
row_class = "user" if is_user else "bot"
av_class = "user-av" if is_user else "bot-av"
bub_class = "user-bubble" if is_user else "bot-bubble"
avatar_icon = "U" if is_user else "⚡"
st.markdown(
f''
f'
{avatar_icon}
'
f'
{msg["content"]}
'
f'
',
unsafe_allow_html=True,
)
# Chat input (always shown at bottom)
if prompt := st.chat_input(
"Message the chatbot...",
disabled=not st.session_state.api_key_set,
):
if not st.session_state.bot:
st.error("Please enter your API key in the sidebar first.")
else:
# Add user message
st.session_state.messages.append({"role": "user", "content": prompt})
# Get bot reply
with st.spinner(""):
reply = st.session_state.bot.chat(prompt)
st.session_state.messages.append({"role": "assistant", "content": reply})
st.rerun()