akhaliq HF Staff commited on
Commit
f250a9c
·
verified ·
1 Parent(s): b406b81

Upload src/streamlit_app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +209 -38
src/streamlit_app.py CHANGED
@@ -1,40 +1,211 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
 
 
 
4
  import streamlit as st
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ast
2
+ import operator as op
3
+ import random
4
+ from datetime import datetime
5
+ import json
6
+ import re
7
  import streamlit as st
8
 
9
+ # -----------------------------
10
+ # Utilities
11
+ # -----------------------------
12
+ def safe_eval(expr: str):
13
+ """
14
+ Safely evaluate a simple arithmetic expression using Python's AST.
15
+ Supports: +, -, *, /, %, //, **, parentheses, unary +/-, ints/floats.
16
+ """
17
+ # Map AST operator nodes to actual Python operators
18
+ operators = {
19
+ ast.Add: op.add,
20
+ ast.Sub: op.sub,
21
+ ast.Mult: op.mul,
22
+ ast.Div: op.truediv,
23
+ ast.Mod: op.mod,
24
+ ast.FloorDiv: op.floordiv,
25
+ ast.Pow: op.pow,
26
+ ast.UAdd: op.pos,
27
+ ast.USub: op.neg,
28
+ }
29
+
30
+ def _eval(node):
31
+ if isinstance(node, ast.Expression):
32
+ return _eval(node.body)
33
+ if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
34
+ return node.value
35
+ if isinstance(node, ast.BinOp):
36
+ if type(node.op) not in operators:
37
+ raise ValueError("Unsupported operator.")
38
+ return operators[type(node.op)](_eval(node.left), _eval(node.right))
39
+ if isinstance(node, ast.UnaryOp) and type(node.op) in (ast.UAdd, ast.USub):
40
+ return operators[type(node.op)](_eval(node.operand))
41
+ if isinstance(node, ast.Paren):
42
+ return _eval(node.value)
43
+ raise ValueError("Unsupported expression.")
44
+
45
+ # Allow only a strict subset of characters
46
+ if not re.fullmatch(r"[0-9\.\+\-\*\/%\(\)\s]+", expr):
47
+ raise ValueError("Expression contains invalid characters.")
48
+ try:
49
+ parsed = ast.parse(expr, mode="eval")
50
+ return _eval(parsed)
51
+ except Exception as e:
52
+ raise ValueError(str(e)) from e
53
+
54
+
55
+ def small_talk_response(text: str) -> str:
56
+ """Very simple keyword-based small talk."""
57
+ low = text.lower()
58
+
59
+ if any(w in low for w in ["hi", "hello", "hey"]):
60
+ return "Hello! How can I help you today?"
61
+
62
+ if "time" in low:
63
+ return f"The current time is {datetime.now().strftime('%H:%M:%S')}."
64
+
65
+ if "date" in low or "day" in low:
66
+ return f"Today is {datetime.now().strftime('%A, %B %d, %Y')}."
67
+
68
+ if "joke" in low:
69
+ jokes = [
70
+ "Why do programmers prefer dark mode? Because light attracts bugs.",
71
+ "I told my computer I needed a break, and it said 'No problem, I'll go to sleep.'",
72
+ "There are only 10 types of people in the world: those who understand binary and those who don’t.",
73
+ ]
74
+ return random.choice(jokes)
75
+
76
+ if "weather" in low:
77
+ return "I can't check live weather here, but I hope it's nice where you are!"
78
+
79
+ if "name" in low:
80
+ return "I'm Streamlit SimpleBot. Nice to meet you!"
81
+
82
+ return (
83
+ "I'm a simple small-talk bot. You can ask me for the time, a joke, my name, "
84
+ "or switch to Calculator mode for math."
85
+ )
86
+
87
+
88
+ def friendly_helper(text: str) -> str:
89
+ """A very simple friendly helper with a few smarts."""
90
+ low = text.lower().strip()
91
+
92
+ # Try to detect plain math even in Friendly mode if it's clearly an expression
93
+ if re.fullmatch(r"[0-9\.\+\-\*\/%\(\)\s]+", text):
94
+ try:
95
+ result = safe_eval(text)
96
+ return f"Here's the result: {result}"
97
+ except Exception:
98
+ pass
99
+
100
+ # Simple tips
101
+ if low.endswith("?"):
102
+ return (
103
+ "That's a great question! I'm a lightweight local bot, so I can handle small talk and simple math. "
104
+ "Try asking for the time, a joke, or enter a math expression like (2+3)*4."
105
+ )
106
+
107
+ # If user asks for help
108
+ if "help" in low:
109
+ return "I can chat casually and do quick calculations. Switch modes from the sidebar to explore."
110
+
111
+ # Default friendly echo with a light rephrase
112
+ return f"I hear you: '{text}'. How would you like me to help with that?"
113
+
114
+
115
+ def calculator_response(text: str) -> str:
116
+ """Use safe_eval to compute the result or provide guidance."""
117
+ try:
118
+ result = safe_eval(text)
119
+ return f"{result}"
120
+ except Exception as e:
121
+ return (
122
+ "I can calculate expressions with +, -, *, /, %, //, ** and parentheses.\n"
123
+ "Example: (2 + 3) * 4\n"
124
+ f"Error: {e}"
125
+ )
126
+
127
+
128
+ def generate_reply(user_text: str, mode: str) -> str:
129
+ """Route user input to the selected bot mode."""
130
+ if mode == "Echo":
131
+ return f"You said: {user_text}"
132
+ if mode == "Calculator":
133
+ return calculator_response(user_text)
134
+ if mode == "Small talk":
135
+ return small_talk_response(user_text)
136
+ # Default: Friendly helper
137
+ return friendly_helper(user_text)
138
+
139
+
140
+ def init_chat(greeting: str):
141
+ st.session_state.messages = [{"role": "assistant", "content": greeting}]
142
+
143
+
144
+ def export_chat_as_text(messages) -> str:
145
+ lines = []
146
+ for m in messages:
147
+ speaker = "You" if m["role"] == "user" else "Assistant"
148
+ lines.append(f"{speaker}: {m['content']}")
149
+ return "\n\n".join(lines)
150
+
151
+
152
+ # -----------------------------
153
+ # App UI
154
+ # -----------------------------
155
+ st.set_page_config(page_title="Simple Streamlit Chatbot", page_icon="💬", layout="centered")
156
+
157
+ st.title("💬 Simple Streamlit Chatbot")
158
+
159
+ with st.sidebar:
160
+ st.header("Settings")
161
+ mode = st.radio(
162
+ "Bot mode",
163
+ ["Friendly", "Echo", "Calculator", "Small talk"],
164
+ index=0,
165
+ help="Choose how the bot should respond.",
166
+ key="mode_selection",
167
+ )
168
+
169
+ # Initialize state and allow clearing chat
170
+ if "messages" not in st.session_state:
171
+ init_chat("Hi! I'm a simple chatbot. Ask me something, or try me in Calculator mode.")
172
+
173
+ if st.button("Clear conversation", type="primary", use_container_width=True):
174
+ init_chat("Chat cleared. How can I help you now?")
175
+ st.rerun()
176
+
177
+ # Download conversation
178
+ chat_text = export_chat_as_text(st.session_state.messages)
179
+ st.download_button(
180
+ "Download conversation",
181
+ data=chat_text.encode("utf-8"),
182
+ file_name="conversation.txt",
183
+ mime="text/plain",
184
+ use_container_width=True,
185
+ )
186
+
187
+ st.caption("This chatbot runs locally with simple rule-based logic.")
188
+
189
+
190
+ # -----------------------------
191
+ # Message history
192
+ # -----------------------------
193
+ for msg in st.session_state.messages:
194
+ with st.chat_message(msg["role"]):
195
+ st.write(msg["content"])
196
+
197
+ # -----------------------------
198
+ # Input box
199
+ # -----------------------------
200
+ prompt = st.chat_input("Type your message here...")
201
+
202
+ if prompt:
203
+ # Store the user's message
204
+ st.session_state.messages.append({"role": "user", "content": prompt})
205
+
206
+ # Generate and store the assistant's reply
207
+ reply = generate_reply(prompt, st.session_state.get("mode_selection", "Friendly"))
208
+ st.session_state.messages.append({"role": "assistant", "content": reply})
209
+
210
+ # Rerun to display the new messages
211
+ st.rerun()