laichai commited on
Commit
2139f3d
·
verified ·
1 Parent(s): 7c6d1b0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -74
app.py CHANGED
@@ -9,12 +9,10 @@ import requests
9
  from PIL import Image
10
  from gtts import gTTS
11
  from duckduckgo_search import DDGS
12
- import base64
13
- import tempfile
14
 
15
- # -----------------------------------------------------------------------------
16
- # 1. PAGE CONFIGURATION
17
- # -----------------------------------------------------------------------------
18
  st.set_page_config(
19
  page_title="H2 Physics Feynman Bot",
20
  page_icon="⚛️",
@@ -22,18 +20,13 @@ st.set_page_config(
22
  initial_sidebar_state="expanded"
23
  )
24
 
25
- # -----------------------------------------------------------------------------
26
- # 2. HELPER FUNCTIONS (Optimized for Docker)
27
- # -----------------------------------------------------------------------------
28
-
29
  @st.cache_data(show_spinner=False, ttl=3600)
30
  def generate_audio(text):
31
  """Generates MP3 audio from text, skipping code/image tags."""
32
- # Clean text so the bot doesn't read code or tags out loud
33
  clean_text = re.sub(r'```.*?```', 'I have generated a graph.', text, flags=re.DOTALL)
34
  clean_text = re.sub(r'\[IMAGE:.*?\]', 'Here is a diagram.', clean_text)
35
 
36
- # Limit text length for audio generation
37
  if len(clean_text) > 1000:
38
  clean_text = clean_text[:1000] + "..."
39
 
@@ -47,6 +40,7 @@ def generate_audio(text):
47
  st.error(f"Audio generation error: {e}")
48
  return None
49
 
 
50
  def google_search_api(query, api_key, cx):
51
  """Helper: Performs a single Google Search."""
52
  try:
@@ -95,44 +89,102 @@ def search_image(query):
95
  """MASTER FUNCTION: Google Search -> DuckDuckGo"""
96
  cx = os.environ.get("GOOGLE_CX", st.secrets.get("GOOGLE_CX", ""))
97
 
98
- # 1. Try Google Key 1
99
  key1 = os.environ.get("GOOGLE_SEARCH_KEY", st.secrets.get("GOOGLE_SEARCH_KEY", ""))
100
  if key1 and cx:
101
  url = google_search_api(query, key1, cx)
102
  if url:
103
  return url
104
 
105
- # 2. Try Google Key 2
106
  key2 = os.environ.get("GOOGLE_SEARCH_KEY_2", st.secrets.get("GOOGLE_SEARCH_KEY_2", ""))
107
  if key2 and cx:
108
  url = google_search_api(query, key2, cx)
109
  if url:
110
  return url
111
 
112
- # 3. Fallback to DuckDuckGo
113
  return duckduckgo_search_api(query)
114
 
 
115
  def execute_plotting_code(code_snippet):
116
  """Execute plotting code in a safe environment."""
117
  try:
118
- fig, ax = plt.subplots(figsize=(8, 4))
119
- local_env = {'plt': plt, 'np': np, 'fig': fig, 'ax': ax}
120
- exec(code_snippet, {}, local_env)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  st.pyplot(fig)
 
 
122
  plt.close(fig)
 
123
  except Exception as e:
124
  st.error(f"Graph Error: {str(e)[:200]}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
 
126
  def display_message(role, content, enable_voice=False):
127
  """Display chat message with all features."""
128
  with st.chat_message(role):
129
 
130
  text_to_display = content
131
 
132
- # 1. Check for Python Code
133
- code_match = re.search(r'```python(.*?)```', content, re.DOTALL)
134
- if code_match and role == "assistant":
135
- text_to_display = text_to_display.replace(code_match.group(0), "")
 
 
 
136
 
137
  # 2. Check for [IMAGE: query] Tags
138
  image_match = re.search(r'\[IMAGE:\s*(.*?)\]', text_to_display, re.IGNORECASE)
@@ -147,11 +199,15 @@ def display_message(role, content, enable_voice=False):
147
  # --- DISPLAY TEXT ---
148
  st.markdown(text_to_display)
149
 
150
- # --- DISPLAY CODE & GRAPH ---
151
- if code_match and role == "assistant":
152
- with st.expander("📊 Show Graph Code"):
153
- st.code(code_match.group(1), language='python')
154
- execute_plotting_code(code_match.group(1))
 
 
 
 
155
 
156
  # --- DISPLAY IMAGE ---
157
  if image_match and role == "assistant":
@@ -169,7 +225,7 @@ def display_message(role, content, enable_voice=False):
169
  audio_bytes = generate_audio(text_to_display)
170
  if audio_bytes:
171
  st.audio(audio_bytes, format='audio/mp3')
172
-
173
  def call_groq_api(api_key, messages, max_tokens=2000):
174
  """Call Groq API - FREE tier available (10,000 requests/month)."""
175
  url = "https://api.groq.com/openai/v1/chat/completions"
@@ -187,22 +243,17 @@ def call_groq_api(api_key, messages, max_tokens=2000):
187
  "role": "system",
188
  "content": msg["content"]
189
  })
190
- elif msg["role"] == "user":
191
  formatted_messages.append({
192
- "role": "user",
193
- "content": msg["content"]
194
- })
195
- elif msg["role"] == "assistant":
196
- formatted_messages.append({
197
- "role": "assistant",
198
  "content": msg["content"]
199
  })
200
 
201
- # Try multiple models in order of preference
202
  models_to_try = [
203
- "llama-3.1-8b-instant", # Fast, free, good for education
204
- "llama-3.2-3b-preview", # Smaller, faster alternative
205
- "mixtral-8x7b-32768", # Higher capacity if available
206
  ]
207
 
208
  for model in models_to_try:
@@ -221,7 +272,6 @@ def call_groq_api(api_key, messages, max_tokens=2000):
221
  result = response.json()
222
  return result["choices"][0]["message"]["content"]
223
  elif response.status_code == 429:
224
- # Rate limit, try next model
225
  continue
226
  elif response.status_code == 401:
227
  return "❌ Invalid API Key. Please check your Groq API key."
@@ -229,16 +279,13 @@ def call_groq_api(api_key, messages, max_tokens=2000):
229
  return "❌ Payment required. The free model might be unavailable. Try again later."
230
 
231
  except requests.exceptions.Timeout:
232
- continue # Try next model if timeout
233
  except Exception as e:
234
- continue # Try next model
235
 
236
  return "⚠️ All models are currently unavailable. Please try again in a few moments."
237
 
238
- # -----------------------------------------------------------------------------
239
- # 3. SYSTEM INSTRUCTIONS
240
- # -----------------------------------------------------------------------------
241
-
242
  SYSTEM_INSTRUCTIONS = """You are Richard Feynman, the Nobel Prize-winning physicist, now serving as a tutor for Singapore H2 Physics (syllabus 9478).
243
 
244
  **CORE DIRECTIVE:**
@@ -246,26 +293,46 @@ SYSTEM_INSTRUCTIONS = """You are Richard Feynman, the Nobel Prize-winning physic
246
  - Reject topics not in the syllabus
247
  - Teach with clarity, enthusiasm, and the Feynman method
248
 
249
- **YOUR TOOLS:**
250
- 1. **GRAPHS:** When asked to plot or visualize data, write Python code using matplotlib/numpy inside ```python code blocks.
251
- 2. **DIAGRAMS:** When you need to show a diagram, use the tag: [IMAGE: search query]
252
- Example: "Here's the setup: [IMAGE: double slit experiment diagram]"
253
- DO NOT use regular markdown images.
 
 
 
 
254
 
255
- **TEACHING METHOD:**
256
- 1. **ASK ONE QUESTION AT A TIME** - keep it simple
257
- 2. **USE ANALOGIES** - explain complex concepts with everyday examples
258
- 3. **GUIDE, DON'T GIVE ANSWERS** - ask leading questions
259
- 4. **VALIDATE UNDERSTANDING** - check frequently if the student follows
260
- 5. **"I GIVE UP" CLAUSE** - only give full solutions when student explicitly says "I give up"
261
- 6. **SUMMARIZE** - after each concept, provide a clear summary in a blockquote (>)
 
 
 
 
 
 
 
 
 
 
262
 
 
 
 
 
 
 
 
263
  **FORMATTING:**
264
- - Use LaTeX for equations: $F = ma$
265
  - Use **bold** for key terms
266
  - Keep responses concise but thorough
267
  - Be enthusiastic and encouraging
268
- - Use emojis sparingly for emphasis ⚛️📚🔬
269
 
270
  **TOPICS COVERED (9478 Syllabus):**
271
  1. Measurement & Uncertainty
@@ -279,11 +346,10 @@ SYSTEM_INSTRUCTIONS = """You are Richard Feynman, the Nobel Prize-winning physic
279
  9. Electricity & DC Circuits
280
  10. Electromagnetism
281
  11. Modern Physics (Quantum/Nuclear)
282
- """
283
 
284
- # -----------------------------------------------------------------------------
285
- # 4. SIDEBAR CONFIGURATION
286
- # -----------------------------------------------------------------------------
287
  with st.sidebar:
288
  # Header with Feynman image
289
  col1, col2 = st.columns([1, 3])
@@ -417,11 +483,7 @@ with st.sidebar:
417
  st.divider()
418
  st.caption("Made with ❤️ for H2 Physics students | Powered by Groq AI")
419
 
420
- # -----------------------------------------------------------------------------
421
- # 5. MAIN CHAT INTERFACE
422
- # -----------------------------------------------------------------------------
423
-
424
- # Initialize session state
425
  if "messages" not in st.session_state:
426
  st.session_state.messages = [
427
  {"role": "system", "content": SYSTEM_INSTRUCTIONS},
@@ -429,14 +491,14 @@ if "messages" not in st.session_state:
429
  **Hello JPJC Physics students! I'm Richard Feynman, ready to help you master H2 Physics!** ⚛️
430
 
431
  I can:
432
- - 📊 **Plot graphs** using Python
433
  - 🖼️ **Find diagrams** through web search
434
  - 💬 **Explain concepts** with analogies
435
  - ❓ **Ask questions** to test your understanding
436
 
437
  **What would you like to learn today?**
438
- *You can ask me anything from the H2 Physics syllabus, upload images of problems, or ask me to explain a specific topic.*
439
  """}
 
440
  ]
441
 
442
  # Title and mode indicator
@@ -565,9 +627,7 @@ st.divider()
565
  st.markdown("""
566
  <div style="text-align: center; color: #888; font-size: 0.9em;">
567
  <p><strong>H2 Physics Feynman Tutor</strong> | Singapore H2 Physics (9478) Syllabus</p>
568
- <p>Powered by <a href="https://groq.com" target="_blank">Groq AI</a> |
569
- <a href="https://www.seab.gov.sg/docs/default-source/national-examinations/syllabus/alevel/2024syllabus/9478_y24_sy.pdf" target="_blank">Official Syllabus</a> |
570
- <a href="https://github.com" target="_blank">GitHub</a></p>
571
  <p style="font-size: 0.8em;">Disclaimer: This is an AI tutoring assistant. Always verify with official syllabus documents.</p>
572
  </div>
573
- """, unsafe_allow_html=True)
 
9
  from PIL import Image
10
  from gtts import gTTS
11
  from duckduckgo_search import DDGS
12
+ import warnings
13
+ warnings.filterwarnings('ignore')
14
 
15
+ # Page Configuration
 
 
16
  st.set_page_config(
17
  page_title="H2 Physics Feynman Bot",
18
  page_icon="⚛️",
 
20
  initial_sidebar_state="expanded"
21
  )
22
 
23
+ # Audio generation
 
 
 
24
  @st.cache_data(show_spinner=False, ttl=3600)
25
  def generate_audio(text):
26
  """Generates MP3 audio from text, skipping code/image tags."""
 
27
  clean_text = re.sub(r'```.*?```', 'I have generated a graph.', text, flags=re.DOTALL)
28
  clean_text = re.sub(r'\[IMAGE:.*?\]', 'Here is a diagram.', clean_text)
29
 
 
30
  if len(clean_text) > 1000:
31
  clean_text = clean_text[:1000] + "..."
32
 
 
40
  st.error(f"Audio generation error: {e}")
41
  return None
42
 
43
+ # Image search functions
44
  def google_search_api(query, api_key, cx):
45
  """Helper: Performs a single Google Search."""
46
  try:
 
89
  """MASTER FUNCTION: Google Search -> DuckDuckGo"""
90
  cx = os.environ.get("GOOGLE_CX", st.secrets.get("GOOGLE_CX", ""))
91
 
 
92
  key1 = os.environ.get("GOOGLE_SEARCH_KEY", st.secrets.get("GOOGLE_SEARCH_KEY", ""))
93
  if key1 and cx:
94
  url = google_search_api(query, key1, cx)
95
  if url:
96
  return url
97
 
 
98
  key2 = os.environ.get("GOOGLE_SEARCH_KEY_2", st.secrets.get("GOOGLE_SEARCH_KEY_2", ""))
99
  if key2 and cx:
100
  url = google_search_api(query, key2, cx)
101
  if url:
102
  return url
103
 
 
104
  return duckduckgo_search_api(query)
105
 
106
+ # Graph plotting function - FIXED VERSION
107
  def execute_plotting_code(code_snippet):
108
  """Execute plotting code in a safe environment."""
109
  try:
110
+ # Clear any existing plots
111
+ plt.close('all')
112
+
113
+ # Create a new figure
114
+ fig, ax = plt.subplots(figsize=(10, 6))
115
+
116
+ # Prepare execution environment
117
+ namespace = {
118
+ 'plt': plt,
119
+ 'np': np,
120
+ 'ax': ax,
121
+ 'fig': fig,
122
+ 'math': __import__('math')
123
+ }
124
+
125
+ # Clean the code
126
+ cleaned_code = code_snippet.strip()
127
+
128
+ # Execute the code
129
+ exec(cleaned_code, namespace)
130
+
131
+ # Get current axis
132
+ ax = plt.gca()
133
+
134
+ # Ensure proper formatting
135
+ if not ax.get_xlabel():
136
+ ax.set_xlabel('X-axis', fontsize=12)
137
+ if not ax.get_ylabel():
138
+ ax.set_ylabel('Y-axis', fontsize=12)
139
+ if not ax.get_title():
140
+ ax.set_title('Physics Graph', fontsize=14)
141
+
142
+ # Ensure grid is visible
143
+ ax.grid(True, linestyle='--', alpha=0.6)
144
+
145
+ # Ensure legend if labels exist
146
+ if ax.get_legend_handles_labels()[1]:
147
+ ax.legend()
148
+
149
+ # Display the plot
150
  st.pyplot(fig)
151
+
152
+ # Close the figure
153
  plt.close(fig)
154
+
155
  except Exception as e:
156
  st.error(f"Graph Error: {str(e)[:200]}")
157
+
158
+ # Fallback: Create a simple sine wave
159
+ try:
160
+ fig, ax = plt.subplots(figsize=(10, 6))
161
+ x = np.linspace(0, 2*np.pi, 100)
162
+ y = np.sin(x)
163
+ ax.plot(x, y, 'b-', linewidth=2, label='sin(x)')
164
+ ax.set_xlabel('Angle (radians)', fontsize=12)
165
+ ax.set_ylabel('Value', fontsize=12)
166
+ ax.set_title('Sample Graph (Fallback)', fontsize=14)
167
+ ax.grid(True, linestyle='--', alpha=0.6)
168
+ ax.legend()
169
+ st.pyplot(fig)
170
+ plt.close(fig)
171
+ except:
172
+ st.warning("Could not generate graph. Please check the Python code.")
173
 
174
+ # Display message function
175
  def display_message(role, content, enable_voice=False):
176
  """Display chat message with all features."""
177
  with st.chat_message(role):
178
 
179
  text_to_display = content
180
 
181
+ # 1. Extract and handle Python code blocks
182
+ code_pattern = r'```python\s*(.*?)```'
183
+ code_matches = list(re.finditer(code_pattern, content, re.DOTALL))
184
+
185
+ # Remove code blocks from displayed text
186
+ for match in reversed(code_matches):
187
+ text_to_display = text_to_display.replace(match.group(0), "")
188
 
189
  # 2. Check for [IMAGE: query] Tags
190
  image_match = re.search(r'\[IMAGE:\s*(.*?)\]', text_to_display, re.IGNORECASE)
 
199
  # --- DISPLAY TEXT ---
200
  st.markdown(text_to_display)
201
 
202
+ # --- EXECUTE AND DISPLAY GRAPHS ---
203
+ if code_matches and role == "assistant":
204
+ for match in code_matches:
205
+ code_content = match.group(1).strip()
206
+ if code_content:
207
+ with st.expander("📊 Generated Graph", expanded=True):
208
+ execute_plotting_code(code_content)
209
+ with st.expander("📝 View Python Code"):
210
+ st.code(code_content, language='python')
211
 
212
  # --- DISPLAY IMAGE ---
213
  if image_match and role == "assistant":
 
225
  audio_bytes = generate_audio(text_to_display)
226
  if audio_bytes:
227
  st.audio(audio_bytes, format='audio/mp3')
228
+ # Groq API function
229
  def call_groq_api(api_key, messages, max_tokens=2000):
230
  """Call Groq API - FREE tier available (10,000 requests/month)."""
231
  url = "https://api.groq.com/openai/v1/chat/completions"
 
243
  "role": "system",
244
  "content": msg["content"]
245
  })
246
+ elif msg["role"] in ["user", "assistant"]:
247
  formatted_messages.append({
248
+ "role": msg["role"],
 
 
 
 
 
249
  "content": msg["content"]
250
  })
251
 
252
+ # Try multiple models
253
  models_to_try = [
254
+ "llama-3.1-8b-instant",
255
+ "llama-3.2-3b-preview",
256
+ "mixtral-8x7b-32768",
257
  ]
258
 
259
  for model in models_to_try:
 
272
  result = response.json()
273
  return result["choices"][0]["message"]["content"]
274
  elif response.status_code == 429:
 
275
  continue
276
  elif response.status_code == 401:
277
  return "❌ Invalid API Key. Please check your Groq API key."
 
279
  return "❌ Payment required. The free model might be unavailable. Try again later."
280
 
281
  except requests.exceptions.Timeout:
282
+ continue
283
  except Exception as e:
284
+ continue
285
 
286
  return "⚠️ All models are currently unavailable. Please try again in a few moments."
287
 
288
+ # System Instructions for the AI - Part 5a (Start)
 
 
 
289
  SYSTEM_INSTRUCTIONS = """You are Richard Feynman, the Nobel Prize-winning physicist, now serving as a tutor for Singapore H2 Physics (syllabus 9478).
290
 
291
  **CORE DIRECTIVE:**
 
293
  - Reject topics not in the syllabus
294
  - Teach with clarity, enthusiasm, and the Feynman method
295
 
296
+ **GRAPH GENERATION RULES (CRITICAL):**
297
+ When asked to create a graph, you MUST write COMPLETE, EXECUTABLE Python code that:
298
+ 1. Starts with: import matplotlib.pyplot as plt, import numpy as np
299
+ 2. Creates a figure: plt.figure(figsize=(10, 6)) or fig, ax = plt.subplots(figsize=(10, 6))
300
+ 3. Generates or uses appropriate physics data
301
+ 4. Plots the data with plt.plot(), plt.scatter(), etc.
302
+ 5. Adds proper labels: plt.xlabel(), plt.ylabel(), plt.title()
303
+ 6. Adds grid: plt.grid(True, linestyle='--', alpha=0.6)
304
+ 7. Adds legend if needed: plt.legend()
305
 
306
+ **EXAMPLE OF GOOD GRAPH CODE:**
307
+ import matplotlib.pyplot as plt
308
+ import numpy as np
309
+
310
+ plt.figure(figsize=(10, 6))
311
+ x = np.linspace(0, 10, 100)
312
+ y = np.sin(x)
313
+ plt.plot(x, y, 'b-', linewidth=2, label='sin(x)')
314
+ plt.xlabel('X Variable', fontsize=12)
315
+ plt.ylabel('Y Variable', fontsize=12)
316
+ plt.title('Physics Graph', fontsize=14)
317
+ plt.grid(True, linestyle='--', alpha=0.6)
318
+ plt.legend()
319
+
320
+ **DIAGRAMS:**
321
+ When you need to show a diagram, use: [IMAGE: search query]
322
+ Example: "Here's the setup: [IMAGE: double slit experiment diagram]"
323
 
324
+ **TEACHING METHOD:**
325
+ 1. Ask ONE question at a time
326
+ 2. Use analogies to explain complex concepts
327
+ 3. Guide, don't give answers immediately
328
+ 4. Validate understanding frequently
329
+ 5. Only give full solutions when student says "I give up"
330
+ 6. Summarize each concept with a clear summary in > blockquote
331
  **FORMATTING:**
332
+ - Use LaTeX for equations: $F = ma$, $E = mc^2$
333
  - Use **bold** for key terms
334
  - Keep responses concise but thorough
335
  - Be enthusiastic and encouraging
 
336
 
337
  **TOPICS COVERED (9478 Syllabus):**
338
  1. Measurement & Uncertainty
 
346
  9. Electricity & DC Circuits
347
  10. Electromagnetism
348
  11. Modern Physics (Quantum/Nuclear)
349
+ '''
350
 
351
+ ## **PART 6: SIDEBAR CONFIGURATION**
352
+ # Sidebar Configuration
 
353
  with st.sidebar:
354
  # Header with Feynman image
355
  col1, col2 = st.columns([1, 3])
 
483
  st.divider()
484
  st.caption("Made with ❤️ for H2 Physics students | Powered by Groq AI")
485
 
486
+ # Main Chat Interface
 
 
 
 
487
  if "messages" not in st.session_state:
488
  st.session_state.messages = [
489
  {"role": "system", "content": SYSTEM_INSTRUCTIONS},
 
491
  **Hello JPJC Physics students! I'm Richard Feynman, ready to help you master H2 Physics!** ⚛️
492
 
493
  I can:
494
+ - 📊 **Plot graphs** using Python (ask me to plot any physics equation!)
495
  - 🖼️ **Find diagrams** through web search
496
  - 💬 **Explain concepts** with analogies
497
  - ❓ **Ask questions** to test your understanding
498
 
499
  **What would you like to learn today?**
 
500
  """}
501
+
502
  ]
503
 
504
  # Title and mode indicator
 
627
  st.markdown("""
628
  <div style="text-align: center; color: #888; font-size: 0.9em;">
629
  <p><strong>H2 Physics Feynman Tutor</strong> | Singapore H2 Physics (9478) Syllabus</p>
630
+ <p>Powered by <a href="https://groq.com" target="_blank">Groq AI</a></p>
 
 
631
  <p style="font-size: 0.8em;">Disclaimer: This is an AI tutoring assistant. Always verify with official syllabus documents.</p>
632
  </div>
633
+ """, unsafe_allow_html=True)