Spaces:
Sleeping
Sleeping
| import ast | |
| import operator as op | |
| import random | |
| from datetime import datetime | |
| import json | |
| import re | |
| import streamlit as st | |
| # ----------------------------- | |
| # Utilities | |
| # ----------------------------- | |
| def safe_eval(expr: str): | |
| """ | |
| Safely evaluate a simple arithmetic expression using Python's AST. | |
| Supports: +, -, *, /, %, //, **, parentheses, unary +/-, ints/floats. | |
| """ | |
| # Map AST operator nodes to actual Python operators | |
| operators = { | |
| ast.Add: op.add, | |
| ast.Sub: op.sub, | |
| ast.Mult: op.mul, | |
| ast.Div: op.truediv, | |
| ast.Mod: op.mod, | |
| ast.FloorDiv: op.floordiv, | |
| ast.Pow: op.pow, | |
| ast.UAdd: op.pos, | |
| ast.USub: op.neg, | |
| } | |
| def _eval(node): | |
| if isinstance(node, ast.Expression): | |
| return _eval(node.body) | |
| if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): | |
| return node.value | |
| if isinstance(node, ast.BinOp): | |
| if type(node.op) not in operators: | |
| raise ValueError("Unsupported operator.") | |
| return operators[type(node.op)](_eval(node.left), _eval(node.right)) | |
| if isinstance(node, ast.UnaryOp) and type(node.op) in (ast.UAdd, ast.USub): | |
| return operators[type(node.op)](_eval(node.operand)) | |
| if isinstance(node, ast.Paren): | |
| return _eval(node.value) | |
| raise ValueError("Unsupported expression.") | |
| # Allow only a strict subset of characters | |
| if not re.fullmatch(r"[0-9\.\+\-\*\/%\(\)\s]+", expr): | |
| raise ValueError("Expression contains invalid characters.") | |
| try: | |
| parsed = ast.parse(expr, mode="eval") | |
| return _eval(parsed) | |
| except Exception as e: | |
| raise ValueError(str(e)) from e | |
| def small_talk_response(text: str) -> str: | |
| """Very simple keyword-based small talk.""" | |
| low = text.lower() | |
| if any(w in low for w in ["hi", "hello", "hey"]): | |
| return "Hello! How can I help you today?" | |
| if "time" in low: | |
| return f"The current time is {datetime.now().strftime('%H:%M:%S')}." | |
| if "date" in low or "day" in low: | |
| return f"Today is {datetime.now().strftime('%A, %B %d, %Y')}." | |
| if "joke" in low: | |
| jokes = [ | |
| "Why do programmers prefer dark mode? Because light attracts bugs.", | |
| "I told my computer I needed a break, and it said 'No problem, I'll go to sleep.'", | |
| "There are only 10 types of people in the world: those who understand binary and those who don’t.", | |
| ] | |
| return random.choice(jokes) | |
| if "weather" in low: | |
| return "I can't check live weather here, but I hope it's nice where you are!" | |
| if "name" in low: | |
| return "I'm Streamlit SimpleBot. Nice to meet you!" | |
| return ( | |
| "I'm a simple small-talk bot. You can ask me for the time, a joke, my name, " | |
| "or switch to Calculator mode for math." | |
| ) | |
| def friendly_helper(text: str) -> str: | |
| """A very simple friendly helper with a few smarts.""" | |
| low = text.lower().strip() | |
| # Try to detect plain math even in Friendly mode if it's clearly an expression | |
| if re.fullmatch(r"[0-9\.\+\-\*\/%\(\)\s]+", text): | |
| try: | |
| result = safe_eval(text) | |
| return f"Here's the result: {result}" | |
| except Exception: | |
| pass | |
| # Simple tips | |
| if low.endswith("?"): | |
| return ( | |
| "That's a great question! I'm a lightweight local bot, so I can handle small talk and simple math. " | |
| "Try asking for the time, a joke, or enter a math expression like (2+3)*4." | |
| ) | |
| # If user asks for help | |
| if "help" in low: | |
| return "I can chat casually and do quick calculations. Switch modes from the sidebar to explore." | |
| # Default friendly echo with a light rephrase | |
| return f"I hear you: '{text}'. How would you like me to help with that?" | |
| def calculator_response(text: str) -> str: | |
| """Use safe_eval to compute the result or provide guidance.""" | |
| try: | |
| result = safe_eval(text) | |
| return f"{result}" | |
| except Exception as e: | |
| return ( | |
| "I can calculate expressions with +, -, *, /, %, //, ** and parentheses.\n" | |
| "Example: (2 + 3) * 4\n" | |
| f"Error: {e}" | |
| ) | |
| def generate_reply(user_text: str, mode: str) -> str: | |
| """Route user input to the selected bot mode.""" | |
| if mode == "Echo": | |
| return f"You said: {user_text}" | |
| if mode == "Calculator": | |
| return calculator_response(user_text) | |
| if mode == "Small talk": | |
| return small_talk_response(user_text) | |
| # Default: Friendly helper | |
| return friendly_helper(user_text) | |
| def init_chat(greeting: str): | |
| st.session_state.messages = [{"role": "assistant", "content": greeting}] | |
| def export_chat_as_text(messages) -> str: | |
| lines = [] | |
| for m in messages: | |
| speaker = "You" if m["role"] == "user" else "Assistant" | |
| lines.append(f"{speaker}: {m['content']}") | |
| return "\n\n".join(lines) | |
| # ----------------------------- | |
| # App UI | |
| # ----------------------------- | |
| st.set_page_config(page_title="Simple Streamlit Chatbot", page_icon="💬", layout="centered") | |
| st.title("💬 Simple Streamlit Chatbot") | |
| with st.sidebar: | |
| st.header("Settings") | |
| mode = st.radio( | |
| "Bot mode", | |
| ["Friendly", "Echo", "Calculator", "Small talk"], | |
| index=0, | |
| help="Choose how the bot should respond.", | |
| key="mode_selection", | |
| ) | |
| # Initialize state and allow clearing chat | |
| if "messages" not in st.session_state: | |
| init_chat("Hi! I'm a simple chatbot. Ask me something, or try me in Calculator mode.") | |
| if st.button("Clear conversation", type="primary", use_container_width=True): | |
| init_chat("Chat cleared. How can I help you now?") | |
| st.rerun() | |
| # Download conversation | |
| chat_text = export_chat_as_text(st.session_state.messages) | |
| st.download_button( | |
| "Download conversation", | |
| data=chat_text.encode("utf-8"), | |
| file_name="conversation.txt", | |
| mime="text/plain", | |
| use_container_width=True, | |
| ) | |
| st.caption("This chatbot runs locally with simple rule-based logic.") | |
| # ----------------------------- | |
| # Message history | |
| # ----------------------------- | |
| for msg in st.session_state.messages: | |
| with st.chat_message(msg["role"]): | |
| st.write(msg["content"]) | |
| # ----------------------------- | |
| # Input box | |
| # ----------------------------- | |
| prompt = st.chat_input("Type your message here...") | |
| if prompt: | |
| # Store the user's message | |
| st.session_state.messages.append({"role": "user", "content": prompt}) | |
| # Generate and store the assistant's reply | |
| reply = generate_reply(prompt, st.session_state.get("mode_selection", "Friendly")) | |
| st.session_state.messages.append({"role": "assistant", "content": reply}) | |
| # Rerun to display the new messages | |
| st.rerun() |