""" 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('

🎓 CampusMind AI

', 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( '
' '

CampusMind AI

' 'Free & Fast' '
', 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()