zakerytclarke commited on
Commit
3e65f9e
·
verified ·
1 Parent(s): d142042

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +51 -43
src/streamlit_app.py CHANGED
@@ -1,6 +1,8 @@
 
1
  import os
2
  import re
3
  import time
 
4
  from typing import List, Dict
5
 
6
  import requests
@@ -11,6 +13,13 @@ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
11
  from teapotai import TeapotAI
12
 
13
 
 
 
 
 
 
 
 
14
  # -----------------------
15
  # Branding / Theme
16
  # -----------------------
@@ -31,36 +40,30 @@ st.set_page_config(
31
 
32
  CUSTOM_CSS = f"""
33
  <style>
34
- /* App background */
35
  .stApp {{
36
  background: {TEA_BG};
37
  color: {TEA_TEXT};
38
  }}
39
 
40
- /* Sidebar */
41
  section[data-testid="stSidebar"] {{
42
  background: {TEA_PANEL};
43
  border-right: 1px solid {TEA_BORDER};
44
  }}
45
 
46
- /* Chat bubbles */
47
  div[data-testid="stChatMessage"] {{
48
  border-radius: 16px;
49
  padding: 8px 10px;
50
  }}
51
 
52
- /* Inputs */
53
  .stTextInput > div > div, .stTextArea > div > div {{
54
  border-radius: 12px !important;
55
  }}
56
 
57
- /* Buttons */
58
  .stButton button {{
59
  border-radius: 12px;
60
  border: 1px solid {TEA_BORDER};
61
  }}
62
 
63
- /* Accent-ish links */
64
  a {{
65
  color: {TEA_ACCENT} !important;
66
  }}
@@ -79,7 +82,6 @@ TIMEOUT_SECS = 15
79
  MODEL_TINY = "teapotai/tinyteapot"
80
  MODEL_LLM = "teapotai/teapotllm"
81
 
82
-
83
  DEFAULT_SYSTEM_PROMPT = (
84
  "You are Teapot, an open-source AI assistant optimized for low-end devices, "
85
  "providing short, accurate responses without hallucinating while excelling at "
@@ -96,15 +98,26 @@ DEFAULT_DOCUMENTS = [
96
  # -----------------------
97
  # Helpers
98
  # -----------------------
 
 
 
 
 
 
 
 
 
 
 
99
  def get_brave_key() -> str:
100
- # Streamlit Cloud secrets support + local env var support
101
- return st.secrets.get("BRAVE_API_KEY") if hasattr(st, "secrets") and "BRAVE_API_KEY" in st.secrets else os.getenv("BRAVE_API_KEY")
102
 
103
 
104
  def brave_search_snippets(query: str, top_k: int = 3) -> List[Dict[str, str]]:
105
  brave_api_key = get_brave_key()
106
  if not brave_api_key:
107
- raise RuntimeError("Missing BRAVE_API_KEY (set env var or Streamlit secrets).")
108
 
109
  headers = {"Accept": "application/json", "X-Subscription-Token": brave_api_key}
110
  params = {"q": query, "count": top_k}
@@ -132,7 +145,7 @@ def brave_search_snippets(query: str, top_k: int = 3) -> List[Dict[str, str]]:
132
 
133
  def format_context_from_results(results: List[Dict[str, str]]) -> str:
134
  """
135
- Stable formatting; plus you asked to strip <strong> tags.
136
  """
137
  if not results:
138
  return ""
@@ -143,7 +156,6 @@ def format_context_from_results(results: List[Dict[str, str]]) -> str:
143
  url = re.sub(r"\s+", " ", r.get("url", "")).strip()
144
  snippet = re.sub(r"\s+", " ", r.get("snippet", "")).strip()
145
 
146
- # strip <strong> tags specifically, as requested
147
  title = title.replace("<strong>", "").replace("</strong>", "")
148
  snippet = snippet.replace("<strong>", "").replace("</strong>", "")
149
 
@@ -155,13 +167,28 @@ def format_context_from_results(results: List[Dict[str, str]]) -> str:
155
  return "\n\n".join(blocks)
156
 
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  # -----------------------
159
  # Model / TeapotAI loader
160
  # -----------------------
161
  @st.cache_resource
162
  def load_teapot_ai(model_name: str) -> TeapotAI:
163
  """
164
- Cached per model_name. TinyTeapot will be loaded on startup (we call it once).
165
  """
166
  tokenizer = AutoTokenizer.from_pretrained(model_name)
167
  model = AutoModelForSeq2SeqLM.from_pretrained(model_name)
@@ -170,37 +197,19 @@ def load_teapot_ai(model_name: str) -> TeapotAI:
170
  model.to(device)
171
  model.eval()
172
 
173
- teapot_ai = TeapotAI(
174
  tokenizer=tokenizer,
175
  model=model,
176
  documents=DEFAULT_DOCUMENTS,
177
  )
178
- return teapot_ai
179
-
180
-
181
- def typewriter_render(text: str, container, speed_chars_per_sec: float = 250.0):
182
- """
183
- TeapotAI.query isn't streamed (in this code), so we do a simple typewriter effect.
184
- """
185
- if not text:
186
- container.markdown("")
187
- return
188
- delay = 1.0 / max(speed_chars_per_sec, 1.0)
189
- out = ""
190
- for ch in text:
191
- out += ch
192
- container.markdown(out)
193
- time.sleep(delay)
194
 
195
 
196
  # -----------------------
197
- # UI
198
  # -----------------------
199
- # Header with logo
200
  col1, col2 = st.columns([1, 3], vertical_alignment="center")
201
  with col1:
202
- # Streamlit will fetch the gif directly
203
- st.image(TEAPOT_LOGO_GIF, use_container_width=True)
204
  with col2:
205
  st.markdown("## TeapotAI Chat")
206
  st.caption("Brave Search (top 3 snippets) → context → TeapotAI.query()")
@@ -215,36 +224,36 @@ with st.sidebar:
215
  help="TinyTeapot loads by default. Switching loads the other model (cached).",
216
  )
217
 
218
- system_prompt = st.text_area("System prompt", value=DEFAULT_SYSTEM_PROMPT, height=140)
219
  show_sources = st.checkbox("Show sources/context", value=True)
220
-
221
- # Optional: “typing” effect
222
  typing_effect = st.checkbox("Typing effect", value=True)
223
 
224
 
225
- # Load TinyTeapot on startup, regardless of current selection (your requirement)
226
  _ = load_teapot_ai(MODEL_TINY)
227
 
228
  # Load selected model (cached after first load)
229
  teapot_ai = load_teapot_ai(model_choice)
230
 
 
 
231
  # Chat state
 
232
  if "messages" not in st.session_state:
233
  st.session_state.messages = [] # [{"role": "user"/"assistant", "content": str}]
234
 
235
- # Render history
236
  for m in st.session_state.messages:
237
  with st.chat_message(m["role"]):
238
  st.markdown(m["content"])
239
 
240
- question = st.chat_input("Ask a question… (@sources are fetched via Brave)")
241
 
242
  if question:
243
  st.session_state.messages.append({"role": "user", "content": question})
244
  with st.chat_message("user"):
245
  st.markdown(question)
246
 
247
- # Brave context
248
  try:
249
  results = brave_search_snippets(question, top_k=TOP_K)
250
  context = format_context_from_results(results)
@@ -252,8 +261,7 @@ if question:
252
  results = []
253
  context = ""
254
 
255
- # TeapotAI query (context comes from Brave)
256
- # NOTE: you explicitly want context="" param to hold Brave results after stripping strong tags.
257
  answer = teapot_ai.query(
258
  query=question,
259
  context=context,
 
1
+ # streamlit_app.py
2
  import os
3
  import re
4
  import time
5
+ import warnings
6
  from typing import List, Dict
7
 
8
  import requests
 
13
  from teapotai import TeapotAI
14
 
15
 
16
+ # -----------------------
17
+ # Silence noisy warnings (optional)
18
+ # -----------------------
19
+ warnings.filterwarnings("ignore", message="pkg_resources is deprecated as an API.*")
20
+ warnings.filterwarnings("ignore", message='Field name "schema" in "TeapotTool" shadows.*')
21
+
22
+
23
  # -----------------------
24
  # Branding / Theme
25
  # -----------------------
 
40
 
41
  CUSTOM_CSS = f"""
42
  <style>
 
43
  .stApp {{
44
  background: {TEA_BG};
45
  color: {TEA_TEXT};
46
  }}
47
 
 
48
  section[data-testid="stSidebar"] {{
49
  background: {TEA_PANEL};
50
  border-right: 1px solid {TEA_BORDER};
51
  }}
52
 
 
53
  div[data-testid="stChatMessage"] {{
54
  border-radius: 16px;
55
  padding: 8px 10px;
56
  }}
57
 
 
58
  .stTextInput > div > div, .stTextArea > div > div {{
59
  border-radius: 12px !important;
60
  }}
61
 
 
62
  .stButton button {{
63
  border-radius: 12px;
64
  border: 1px solid {TEA_BORDER};
65
  }}
66
 
 
67
  a {{
68
  color: {TEA_ACCENT} !important;
69
  }}
 
82
  MODEL_TINY = "teapotai/tinyteapot"
83
  MODEL_LLM = "teapotai/teapotllm"
84
 
 
85
  DEFAULT_SYSTEM_PROMPT = (
86
  "You are Teapot, an open-source AI assistant optimized for low-end devices, "
87
  "providing short, accurate responses without hallucinating while excelling at "
 
98
  # -----------------------
99
  # Helpers
100
  # -----------------------
101
+ def st_image_full_width(img_url: str):
102
+ """
103
+ HF Spaces sometimes pins an older streamlit build where st.image doesn't accept
104
+ use_container_width. Fall back to use_column_width.
105
+ """
106
+ try:
107
+ st.image(img_url, use_container_width=True)
108
+ except TypeError:
109
+ st.image(img_url, use_column_width=True)
110
+
111
+
112
  def get_brave_key() -> str:
113
+ # HF Spaces typically provides secrets as env vars.
114
+ return os.getenv("BRAVE_API_KEY") or (st.secrets.get("BRAVE_API_KEY") if hasattr(st, "secrets") else None)
115
 
116
 
117
  def brave_search_snippets(query: str, top_k: int = 3) -> List[Dict[str, str]]:
118
  brave_api_key = get_brave_key()
119
  if not brave_api_key:
120
+ raise RuntimeError("Missing BRAVE_API_KEY (set Space secret or env var).")
121
 
122
  headers = {"Accept": "application/json", "X-Subscription-Token": brave_api_key}
123
  params = {"q": query, "count": top_k}
 
145
 
146
  def format_context_from_results(results: List[Dict[str, str]]) -> str:
147
  """
148
+ Stable formatting + strip <strong> tags as requested.
149
  """
150
  if not results:
151
  return ""
 
156
  url = re.sub(r"\s+", " ", r.get("url", "")).strip()
157
  snippet = re.sub(r"\s+", " ", r.get("snippet", "")).strip()
158
 
 
159
  title = title.replace("<strong>", "").replace("</strong>", "")
160
  snippet = snippet.replace("<strong>", "").replace("</strong>", "")
161
 
 
167
  return "\n\n".join(blocks)
168
 
169
 
170
+ def typewriter_render(text: str, container, speed_chars_per_sec: float = 350.0):
171
+ """
172
+ TeapotAI.query returns a full string; mimic streaming with a typewriter effect.
173
+ """
174
+ if not text:
175
+ container.markdown("")
176
+ return
177
+ delay = 1.0 / max(speed_chars_per_sec, 1.0)
178
+ out = ""
179
+ for ch in text:
180
+ out += ch
181
+ container.markdown(out)
182
+ time.sleep(delay)
183
+
184
+
185
  # -----------------------
186
  # Model / TeapotAI loader
187
  # -----------------------
188
  @st.cache_resource
189
  def load_teapot_ai(model_name: str) -> TeapotAI:
190
  """
191
+ Cached per model_name. TinyTeapot is loaded on startup via an explicit call.
192
  """
193
  tokenizer = AutoTokenizer.from_pretrained(model_name)
194
  model = AutoModelForSeq2SeqLM.from_pretrained(model_name)
 
197
  model.to(device)
198
  model.eval()
199
 
200
+ return TeapotAI(
201
  tokenizer=tokenizer,
202
  model=model,
203
  documents=DEFAULT_DOCUMENTS,
204
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
 
206
 
207
  # -----------------------
208
+ # UI Header
209
  # -----------------------
 
210
  col1, col2 = st.columns([1, 3], vertical_alignment="center")
211
  with col1:
212
+ st_image_full_width(TEAPOT_LOGO_GIF)
 
213
  with col2:
214
  st.markdown("## TeapotAI Chat")
215
  st.caption("Brave Search (top 3 snippets) → context → TeapotAI.query()")
 
224
  help="TinyTeapot loads by default. Switching loads the other model (cached).",
225
  )
226
 
227
+ system_prompt = st.text_area("System prompt", value=DEFAULT_SYSTEM_PROMPT, height=150)
228
  show_sources = st.checkbox("Show sources/context", value=True)
 
 
229
  typing_effect = st.checkbox("Typing effect", value=True)
230
 
231
 
232
+ # Requirement: load tiny model on startup regardless of selection
233
  _ = load_teapot_ai(MODEL_TINY)
234
 
235
  # Load selected model (cached after first load)
236
  teapot_ai = load_teapot_ai(model_choice)
237
 
238
+
239
+ # -----------------------
240
  # Chat state
241
+ # -----------------------
242
  if "messages" not in st.session_state:
243
  st.session_state.messages = [] # [{"role": "user"/"assistant", "content": str}]
244
 
 
245
  for m in st.session_state.messages:
246
  with st.chat_message(m["role"]):
247
  st.markdown(m["content"])
248
 
249
+ question = st.chat_input("Ask a question… (Brave will fetch top 3 snippets)")
250
 
251
  if question:
252
  st.session_state.messages.append({"role": "user", "content": question})
253
  with st.chat_message("user"):
254
  st.markdown(question)
255
 
256
+ # Brave search context
257
  try:
258
  results = brave_search_snippets(question, top_k=TOP_K)
259
  context = format_context_from_results(results)
 
261
  results = []
262
  context = ""
263
 
264
+ # TeapotAI query (context includes Brave results)
 
265
  answer = teapot_ai.query(
266
  query=question,
267
  context=context,