Spaces:
Sleeping
Sleeping
File size: 6,882 Bytes
f250a9c 7aa8120 f250a9c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 | 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() |