Spaces:
Runtime error
Runtime error
| """ | |
| app.py | |
| Streamlit front-end for the Custom AI Chatbot with Memory (Project 1, | |
| DecodeLabs Generative AI Internship). | |
| Key architectural concept: | |
| Streamlit reruns this ENTIRE script top-to-bottom on every user | |
| interaction (every message sent, every button click). A plain Python | |
| list defined here would be wiped and recreated on every rerun -- | |
| which would defeat the whole point of "memory." | |
| The fix is `st.session_state`: a dict-like object Streamlit preserves | |
| across reruns for as long as the browser session stays open. Our | |
| conversation_history list lives there instead of as a bare variable. | |
| """ | |
| import streamlit as st | |
| from chat_engine import get_claude_response | |
| st.set_page_config(page_title="Claude Chatbot with Memory", page_icon="🤖") | |
| st.title("🤖 Custom AI Chatbot with Memory") | |
| st.caption("Project 1 — DecodeLabs Generative AI Internship | Powered by Claude") | |
| # -------------------------------------------------------------------------- | |
| # 1. Initialize persistent memory (this block only runs ONCE per session, | |
| # thanks to the "not in st.session_state" guard) | |
| # -------------------------------------------------------------------------- | |
| if "conversation_history" not in st.session_state: | |
| st.session_state.conversation_history = [] | |
| # -------------------------------------------------------------------------- | |
| # 2. Re-render the existing conversation on every rerun | |
| # (Streamlit has no persistent DOM -- we redraw everything, every time) | |
| # -------------------------------------------------------------------------- | |
| for message in st.session_state.conversation_history: | |
| with st.chat_message(message["role"]): | |
| st.markdown(message["content"]) | |
| # -------------------------------------------------------------------------- | |
| # 3. Capture new user input | |
| # -------------------------------------------------------------------------- | |
| user_input = st.chat_input("Type your message...") | |
| if user_input: | |
| # --- Structural Validation Gate --- | |
| # Never let an empty/whitespace-only string reach the API: Claude | |
| # returns a 400 Bad Request for it, which would crash the app if | |
| # left unguarded. | |
| cleaned_input = user_input.strip() | |
| if not cleaned_input: | |
| st.warning("Please enter a non-empty message.") | |
| else: | |
| # Step 1: Append the user's turn to memory and display it | |
| st.session_state.conversation_history.append( | |
| {"role": "user", "content": cleaned_input} | |
| ) | |
| with st.chat_message("user"): | |
| st.markdown(cleaned_input) | |
| # Step 2: Send the FULL history to Claude and display the reply | |
| with st.chat_message("assistant"): | |
| with st.spinner("Thinking..."): | |
| try: | |
| reply = get_claude_response( | |
| st.session_state.conversation_history | |
| ) | |
| except (ValueError, RuntimeError) as exc: | |
| reply = f"⚠️ {exc}" | |
| st.markdown(reply) | |
| # Step 3: Append Claude's reply to memory so the NEXT turn has | |
| # full context of this exchange too. | |
| st.session_state.conversation_history.append( | |
| {"role": "assistant", "content": reply} | |
| ) | |
| # -------------------------------------------------------------------------- | |
| # 4. Sidebar utility: reset the conversation | |
| # -------------------------------------------------------------------------- | |
| with st.sidebar: | |
| st.subheader("Session Controls") | |
| st.write(f"Messages in memory: {len(st.session_state.conversation_history)}") | |
| if st.button("🗑️ Clear conversation"): | |
| st.session_state.conversation_history = [] | |
| st.rerun() | |