Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -7,17 +7,20 @@ import io
|
|
7 |
import requests
|
8 |
import re
|
9 |
import os
|
|
|
|
|
10 |
|
11 |
# --- Config ---
|
12 |
-
DEFAULT_API_KEY = "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"
|
13 |
API_URL = "https://openrouter.ai/api/v1/chat/completions"
|
14 |
MODEL = "mistralai/mistral-7b-instruct"
|
15 |
|
16 |
# Set page config
|
17 |
st.set_page_config(
|
18 |
-
page_title="π¬
|
19 |
layout="centered",
|
20 |
-
page_icon="π¬"
|
|
|
21 |
)
|
22 |
|
23 |
# Custom CSS for styling
|
@@ -80,279 +83,481 @@ st.markdown("""
|
|
80 |
padding: 15px;
|
81 |
margin: 15px 0;
|
82 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
</style>
|
84 |
""", unsafe_allow_html=True)
|
85 |
|
86 |
# Header
|
87 |
-
st.markdown('<p class="header">π¬
|
88 |
|
89 |
# Introduction
|
90 |
st.markdown("""
|
91 |
<div style="text-align: center; margin-bottom: 30px;">
|
92 |
-
<p style="font-size: 18px;">
|
93 |
-
|
94 |
</div>
|
95 |
""", unsafe_allow_html=True)
|
96 |
|
97 |
# API Key Setup
|
98 |
-
st.
|
99 |
-
st.markdown("
|
100 |
-
st.info("You need an API key from [OpenRouter](https://openrouter.ai/) to use this tool. Get a free key and paste it below.")
|
101 |
-
|
102 |
-
api_key = st.text_input("Enter your OpenRouter API Key:", type="password",
|
103 |
-
help="Get your API key from https://openrouter.ai/keys",
|
104 |
-
value=os.getenv("OPENROUTER_API_KEY", DEFAULT_API_KEY))
|
105 |
-
|
106 |
-
# Features in columns
|
107 |
-
col1, col2, col3 = st.columns(3)
|
108 |
-
with col1:
|
109 |
-
st.markdown("""
|
110 |
-
<div style="text-align: center;">
|
111 |
-
<h4>π Comprehensive Analysis</h4>
|
112 |
-
<p>Checks for all essential lab report sections</p>
|
113 |
-
</div>
|
114 |
-
""", unsafe_allow_html=True)
|
115 |
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
<h4>π― Smart Scoring</h4>
|
120 |
-
<p>Grades your report on completeness and structure</p>
|
121 |
-
</div>
|
122 |
-
""", unsafe_allow_html=True)
|
123 |
|
124 |
-
|
125 |
-
st.markdown(""
|
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 |
-
lab_text = st.text_area("", lab_text, height=300, label_visibility="collapsed")
|
158 |
|
159 |
-
#
|
160 |
-
|
161 |
-
|
162 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
163 |
|
164 |
-
|
165 |
-
|
|
|
166 |
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
- Objective
|
171 |
-
- Hypothesis
|
172 |
-
- Materials
|
173 |
-
- Procedure
|
174 |
-
- Observations
|
175 |
-
- Results
|
176 |
-
- Conclusion
|
177 |
-
- References
|
178 |
-
|
179 |
-
2. **Completeness Score**:
|
180 |
-
- Assign a numerical score from 1-10 based on completeness
|
181 |
-
- Justify the score based on missing sections and content quality
|
182 |
|
183 |
-
|
184 |
-
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
headers = {
|
199 |
-
"Authorization": f"Bearer {api_key}",
|
200 |
-
"Content-Type": "application/json"
|
201 |
-
}
|
202 |
-
payload = {
|
203 |
-
"model": MODEL,
|
204 |
-
"messages": [
|
205 |
-
{"role": "system", "content": "You are a helpful science teacher providing detailed lab report feedback."},
|
206 |
-
{"role": "user", "content": prompt}
|
207 |
-
]
|
208 |
-
}
|
209 |
-
try:
|
210 |
-
response = requests.post(API_URL, headers=headers, json=payload, timeout=120)
|
211 |
-
response.raise_for_status()
|
212 |
-
return response.json()['choices'][0]['message']['content']
|
213 |
-
except requests.exceptions.HTTPError as err:
|
214 |
-
st.error(f"API Error: {err.response.status_code} - {err.response.text}")
|
215 |
-
return None
|
216 |
-
except Exception as e:
|
217 |
-
st.error(f"Error connecting to AI service: {str(e)}")
|
218 |
-
return None
|
219 |
-
|
220 |
-
if st.button("π§ͺ Analyze Report", use_container_width=True):
|
221 |
-
with st.spinner("π Analyzing report with AI. This may take 20-30 seconds..."):
|
222 |
-
result = query_ai(full_prompt, api_key)
|
223 |
-
|
224 |
-
if result:
|
225 |
-
st.success("β
Analysis Complete!")
|
226 |
-
st.balloons()
|
227 |
-
|
228 |
-
# Extract score using regex
|
229 |
-
score_match = re.search(r"Completeness Score:\s*(\d+)/10", result, re.IGNORECASE)
|
230 |
-
score = int(score_match.group(1)) if score_match else None
|
231 |
-
|
232 |
-
# Display score in a card
|
233 |
-
if score is not None:
|
234 |
-
with st.container():
|
235 |
-
st.markdown('<div class="score-card">', unsafe_allow_html=True)
|
236 |
-
|
237 |
-
# Create columns for score visualization
|
238 |
-
col1, col2 = st.columns([1, 3])
|
239 |
-
|
240 |
-
with col1:
|
241 |
-
st.markdown(f"<h2 style='text-align: center; color: #28b463;'>{score}/10</h2>",
|
242 |
-
unsafe_allow_html=True)
|
243 |
-
st.markdown("<h4 style='text-align: center;'>Completeness Score</h4>",
|
244 |
-
unsafe_allow_html=True)
|
245 |
-
|
246 |
-
with col2:
|
247 |
-
# Create a color gradient based on score
|
248 |
-
if score >= 8:
|
249 |
-
color = "#28b463" # Green
|
250 |
-
elif score >= 5:
|
251 |
-
color = "#f39c12" # Orange
|
252 |
-
else:
|
253 |
-
color = "#e74c3c" # Red
|
254 |
-
|
255 |
-
# Display progress bar with styling
|
256 |
-
st.progress(score/10, text=f"{score*10}% complete")
|
257 |
-
st.markdown(
|
258 |
-
f"<style>"
|
259 |
-
f".stProgress > div > div > div {{"
|
260 |
-
f" background-color: {color} !important;"
|
261 |
-
f" border-radius: 10px;"
|
262 |
-
f"}}"
|
263 |
-
f"</style>",
|
264 |
-
unsafe_allow_html=True
|
265 |
-
)
|
266 |
-
|
267 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
268 |
-
|
269 |
-
# Display AI analysis with formatting
|
270 |
-
st.markdown("## π Analysis Results")
|
271 |
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
"Improvement Tips": None,
|
276 |
-
"Detailed Feedback": None
|
277 |
-
}
|
278 |
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
sections[current_section] = []
|
284 |
-
elif "### Improvement Tips:" in line:
|
285 |
-
current_section = "Improvement Tips"
|
286 |
-
sections[current_section] = []
|
287 |
-
elif "### Detailed Feedback:" in line:
|
288 |
-
current_section = "Detailed Feedback"
|
289 |
-
sections[current_section] = []
|
290 |
-
elif current_section and line.strip():
|
291 |
-
sections[current_section].append(line)
|
292 |
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
|
|
|
|
298 |
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
|
|
303 |
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
|
308 |
-
#
|
309 |
-
|
310 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
311 |
|
312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
313 |
st.markdown("---")
|
314 |
-
st.markdown('<p class="subheader"
|
315 |
|
316 |
-
col1, col2 = st.columns(
|
317 |
with col1:
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
323 |
|
324 |
-
|
325 |
-
with st.
|
326 |
-
|
327 |
-
|
|
|
|
|
|
|
|
|
|
|
328 |
|
329 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
330 |
|
331 |
-
|
332 |
-
|
333 |
-
|
334 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
335 |
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
st.
|
344 |
-
|
345 |
-
st.markdown("""
|
346 |
-
**Title:** Effect of Temperature on Enzyme Activity
|
347 |
-
**Objective:** To investigate how temperature affects catalase enzyme activity
|
348 |
-
**Hypothesis:** Enzyme activity will increase with temperature up to 37Β°C, then decrease
|
349 |
-
**Materials:** Test tubes, hydrogen peroxide, liver extract, thermometer
|
350 |
-
**Procedure:**
|
351 |
-
1. Prepare test tubes at 5 different temperatures
|
352 |
-
2. Add equal amounts of hydrogen peroxide and liver extract
|
353 |
-
3. Measure oxygen production
|
354 |
-
**Observations:** More bubbles at 37Β°C compared to lower or higher temperatures
|
355 |
-
**Conclusion:** Enzyme activity peaks at body temperature
|
356 |
-
""")
|
357 |
-
|
358 |
-
st.info("π Upload your own lab report to get a personalized analysis!")
|
|
|
7 |
import requests
|
8 |
import re
|
9 |
import os
|
10 |
+
from fpdf import FPDF
|
11 |
+
from datetime import datetime
|
12 |
|
13 |
# --- Config ---
|
14 |
+
DEFAULT_API_KEY = "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"
|
15 |
API_URL = "https://openrouter.ai/api/v1/chat/completions"
|
16 |
MODEL = "mistralai/mistral-7b-instruct"
|
17 |
|
18 |
# Set page config
|
19 |
st.set_page_config(
|
20 |
+
page_title="π¬ Science Lab Assistant",
|
21 |
layout="centered",
|
22 |
+
page_icon="π¬",
|
23 |
+
initial_sidebar_state="expanded"
|
24 |
)
|
25 |
|
26 |
# Custom CSS for styling
|
|
|
83 |
padding: 15px;
|
84 |
margin: 15px 0;
|
85 |
}
|
86 |
+
.experiment-card {
|
87 |
+
background: linear-gradient(135deg, #f0f7ff, #e1effe);
|
88 |
+
border-radius: 15px;
|
89 |
+
padding: 20px;
|
90 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
91 |
+
margin-bottom: 20px;
|
92 |
+
transition: all 0.3s;
|
93 |
+
}
|
94 |
+
.experiment-card:hover {
|
95 |
+
transform: translateY(-5px);
|
96 |
+
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
|
97 |
+
}
|
98 |
+
.concept-box {
|
99 |
+
background-color: #ebf5fb;
|
100 |
+
border-left: 5px solid #3498db;
|
101 |
+
padding: 15px;
|
102 |
+
margin: 15px 0;
|
103 |
+
border-radius: 0 8px 8px 0;
|
104 |
+
}
|
105 |
+
[data-testid="stSidebar"] {
|
106 |
+
background: linear-gradient(180deg, #e8f8f5, #d1f2eb) !important;
|
107 |
+
}
|
108 |
</style>
|
109 |
""", unsafe_allow_html=True)
|
110 |
|
111 |
# Header
|
112 |
+
st.markdown('<p class="header">π¬ Science Lab Assistant</p>', unsafe_allow_html=True)
|
113 |
|
114 |
# Introduction
|
115 |
st.markdown("""
|
116 |
<div style="text-align: center; margin-bottom: 30px;">
|
117 |
+
<p style="font-size: 18px;">Your all-in-one science companion! Design experiments, generate reports,
|
118 |
+
and get AI-powered feedback on your lab work.</p>
|
119 |
</div>
|
120 |
""", unsafe_allow_html=True)
|
121 |
|
122 |
# API Key Setup
|
123 |
+
with st.sidebar:
|
124 |
+
st.markdown("## π API Configuration")
|
125 |
+
st.info("You need an API key from [OpenRouter](https://openrouter.ai/) to use this tool. Get a free key and paste it below.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
|
127 |
+
api_key = st.text_input("Enter your OpenRouter API Key:", type="password",
|
128 |
+
help="Get your API key from https://openrouter.ai/keys",
|
129 |
+
value=os.getenv("OPENROUTER_API_KEY", DEFAULT_API_KEY))
|
|
|
|
|
|
|
|
|
130 |
|
131 |
+
st.markdown("---")
|
132 |
+
st.markdown("### π§ͺ Experiment Templates")
|
133 |
+
st.caption("Quickly start with these pre-defined experiments:")
|
134 |
+
|
135 |
+
# Experiment templates
|
136 |
+
experiments = {
|
137 |
+
"Vinegar + Baking Soda": {
|
138 |
+
"hypothesis": "Mixing vinegar and baking soda will produce bubbles due to a chemical reaction.",
|
139 |
+
"concept": "Acid-base reaction producing carbon dioxide."
|
140 |
+
},
|
141 |
+
"Floating Egg": {
|
142 |
+
"hypothesis": "An egg will float in salt water but sink in plain water.",
|
143 |
+
"concept": "Density difference between saltwater and freshwater."
|
144 |
+
},
|
145 |
+
"Lemon Battery": {
|
146 |
+
"hypothesis": "A lemon can produce electricity to power a small LED.",
|
147 |
+
"concept": "Chemical energy conversion to electrical energy."
|
148 |
+
}
|
149 |
+
}
|
150 |
+
|
151 |
+
selected_exp = st.selectbox("Choose an experiment template:",
|
152 |
+
list(experiments.keys()) + ["Custom Experiment"])
|
153 |
+
|
154 |
+
st.markdown("---")
|
155 |
+
st.markdown("### π Science Glossary Helper")
|
156 |
+
term = st.text_input("Enter a science term (e.g., osmosis, catalyst)")
|
157 |
+
if term:
|
158 |
+
with st.spinner("Looking up term..."):
|
159 |
+
ai_response = st.session_state.get('query_ai', lambda p: f"Explain '{term}'")(f"Explain the term '{term}' in simple words for a student.")
|
160 |
+
st.markdown(f"<div class='concept-box'>{ai_response}</div>", unsafe_allow_html=True)
|
161 |
|
162 |
+
# Navigation
|
163 |
+
app_mode = st.radio("Choose Mode:", ["π§ͺ Experiment Assistant", "π Lab Report Analyzer"],
|
164 |
+
horizontal=True, label_visibility="collapsed")
|
|
|
165 |
|
166 |
+
# AI Query Function
|
167 |
+
def query_ai(prompt, api_key):
|
168 |
+
if not api_key:
|
169 |
+
st.error("β οΈ Please enter a valid API key")
|
170 |
+
return None
|
171 |
+
|
172 |
+
headers = {
|
173 |
+
"Authorization": f"Bearer {api_key}",
|
174 |
+
"Content-Type": "application/json"
|
175 |
+
}
|
176 |
+
payload = {
|
177 |
+
"model": MODEL,
|
178 |
+
"messages": [
|
179 |
+
{"role": "system", "content": "You are a helpful science teacher providing detailed explanations."},
|
180 |
+
{"role": "user", "content": prompt}
|
181 |
+
],
|
182 |
+
"temperature": 0.7
|
183 |
+
}
|
184 |
+
try:
|
185 |
+
response = requests.post(API_URL, headers=headers, json=payload, timeout=120)
|
186 |
+
response.raise_for_status()
|
187 |
+
return response.json()['choices'][0]['message']['content']
|
188 |
+
except requests.exceptions.HTTPError as err:
|
189 |
+
st.error(f"API Error: {err.response.status_code} - {err.response.text}")
|
190 |
+
return None
|
191 |
+
except Exception as e:
|
192 |
+
st.error(f"Error connecting to AI service: {str(e)}")
|
193 |
+
return None
|
194 |
|
195 |
+
# Store query_ai in session state for glossary helper
|
196 |
+
if 'query_ai' not in st.session_state:
|
197 |
+
st.session_state.query_ai = query_ai
|
198 |
|
199 |
+
# --- Experiment Assistant Section ---
|
200 |
+
if app_mode == "π§ͺ Experiment Assistant":
|
201 |
+
st.markdown('<p class="subheader">π Design Your Experiment</p>', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
202 |
|
203 |
+
with st.form("experiment_form"):
|
204 |
+
# Pre-fill if template selected
|
205 |
+
if selected_exp != "Custom Experiment" and selected_exp in experiments:
|
206 |
+
default_hypo = experiments[selected_exp]["hypothesis"]
|
207 |
+
concept = experiments[selected_exp]["concept"]
|
208 |
+
exp_name = selected_exp
|
209 |
+
else:
|
210 |
+
default_hypo = ""
|
211 |
+
concept = ""
|
212 |
+
exp_name = st.text_input("Experiment Name", placeholder="e.g., Effect of Temperature on Enzyme Activity")
|
213 |
+
|
214 |
+
hypo = st.text_area("Your Hypothesis", value=default_hypo,
|
215 |
+
placeholder="What do you predict will happen?")
|
216 |
+
|
217 |
+
materials = st.text_area("Materials Needed",
|
218 |
+
placeholder="List all materials needed for this experiment")
|
219 |
+
|
220 |
+
procedure = st.text_area("Procedure Steps",
|
221 |
+
placeholder="Step-by-step instructions for conducting the experiment")
|
222 |
+
|
223 |
+
submit = st.form_submit_button("π Generate Experiment Guide", use_container_width=True)
|
224 |
|
225 |
+
if submit:
|
226 |
+
if not exp_name or not hypo:
|
227 |
+
st.warning("Please provide at least an experiment name and hypothesis")
|
228 |
+
st.stop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
229 |
|
230 |
+
with st.spinner("Designing your experiment guide..."):
|
231 |
+
prompt = f"""
|
232 |
+
Create a comprehensive guide for a science experiment with the following details:
|
|
|
|
|
|
|
233 |
|
234 |
+
Experiment Name: {exp_name}
|
235 |
+
Hypothesis: {hypo}
|
236 |
+
Materials: {materials if materials else 'Not specified'}
|
237 |
+
Procedure: {procedure if procedure else 'Not specified'}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
238 |
|
239 |
+
Please provide:
|
240 |
+
1. A clear explanation of the scientific concept behind the experiment
|
241 |
+
2. Step-by-step instructions for conducting the experiment
|
242 |
+
3. Safety precautions
|
243 |
+
4. Expected results and why they're expected
|
244 |
+
5. How to interpret the results
|
245 |
+
"""
|
246 |
|
247 |
+
explanation = query_ai(prompt, api_key)
|
248 |
+
|
249 |
+
if explanation:
|
250 |
+
st.success("β
Experiment Guide Generated!")
|
251 |
+
st.balloons()
|
252 |
|
253 |
+
# Display explanation
|
254 |
+
st.markdown("### π§ͺ Experiment Guide")
|
255 |
+
st.markdown(f"<div class='tip-box'>{explanation}</div>", unsafe_allow_html=True)
|
256 |
|
257 |
+
# Generate PDF report
|
258 |
+
def generate_pdf_report(exp_name, hypo, explanation, materials, procedure):
|
259 |
+
pdf = FPDF()
|
260 |
+
pdf.add_page()
|
261 |
+
pdf.set_font("Arial", size=12)
|
262 |
+
|
263 |
+
# Title
|
264 |
+
pdf.set_font("Arial", 'B', 16)
|
265 |
+
pdf.cell(200, 10, txt="Science Experiment Guide", ln=True, align='C')
|
266 |
+
pdf.ln(15)
|
267 |
+
|
268 |
+
# Experiment details
|
269 |
+
pdf.set_font("Arial", 'B', 14)
|
270 |
+
pdf.cell(0, 10, txt=f"Experiment: {exp_name}", ln=True)
|
271 |
+
pdf.ln(5)
|
272 |
+
|
273 |
+
pdf.set_font("Arial", 'B', 12)
|
274 |
+
pdf.cell(0, 10, txt="Hypothesis:", ln=True)
|
275 |
+
pdf.set_font("Arial", '', 12)
|
276 |
+
pdf.multi_cell(0, 8, txt=hypo)
|
277 |
+
pdf.ln(5)
|
278 |
+
|
279 |
+
if materials:
|
280 |
+
pdf.set_font("Arial", 'B', 12)
|
281 |
+
pdf.cell(0, 10, txt="Materials:", ln=True)
|
282 |
+
pdf.set_font("Arial", '', 12)
|
283 |
+
pdf.multi_cell(0, 8, txt=materials)
|
284 |
+
pdf.ln(5)
|
285 |
+
|
286 |
+
if procedure:
|
287 |
+
pdf.set_font("Arial", 'B', 12)
|
288 |
+
pdf.cell(0, 10, txt="Procedure:", ln=True)
|
289 |
+
pdf.set_font("Arial", '', 12)
|
290 |
+
pdf.multi_cell(0, 8, txt=procedure)
|
291 |
+
pdf.ln(10)
|
292 |
+
|
293 |
+
pdf.set_font("Arial", 'B', 12)
|
294 |
+
pdf.cell(0, 10, txt="Experiment Guide:", ln=True)
|
295 |
+
pdf.set_font("Arial", '', 12)
|
296 |
+
pdf.multi_cell(0, 8, txt=explanation)
|
297 |
+
|
298 |
+
filename = f"experiment_guide_{datetime.now().strftime('%Y%m%d%H%M%S')}.pdf"
|
299 |
+
pdf.output(filename)
|
300 |
+
return filename
|
301 |
|
302 |
+
pdf_file = generate_pdf_report(exp_name, hypo, explanation, materials, procedure)
|
303 |
+
with open(pdf_file, "rb") as file:
|
304 |
+
st.download_button("π Download Experiment Guide (PDF)", file,
|
305 |
+
file_name=f"{exp_name}_guide.pdf",
|
306 |
+
use_container_width=True)
|
307 |
+
|
308 |
+
# Experiment examples
|
309 |
st.markdown("---")
|
310 |
+
st.markdown('<p class="subheader">π¬ Popular Science Experiments</p>', unsafe_allow_html=True)
|
311 |
|
312 |
+
col1, col2 = st.columns(2)
|
313 |
with col1:
|
314 |
+
with st.container():
|
315 |
+
st.markdown('<div class="experiment-card">', unsafe_allow_html=True)
|
316 |
+
st.markdown("#### π§« Vinegar + Baking Soda")
|
317 |
+
st.markdown("**Hypothesis:** Mixing vinegar and baking soda will produce bubbles due to a chemical reaction.")
|
318 |
+
st.markdown("**Concept:** Acid-base reaction producing carbon dioxide.")
|
319 |
+
if st.button("Try This Experiment", key="vinegar", use_container_width=True):
|
320 |
+
st.session_state.selected_exp = "Vinegar + Baking Soda"
|
321 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
322 |
+
|
323 |
+
with st.container():
|
324 |
+
st.markdown('<div class="experiment-card">', unsafe_allow_html=True)
|
325 |
+
st.markdown("#### π₯ Floating Egg")
|
326 |
+
st.markdown("**Hypothesis:** An egg will float in salt water but sink in plain water.")
|
327 |
+
st.markdown("**Concept:** Density difference between saltwater and freshwater.")
|
328 |
+
if st.button("Try This Experiment", key="egg", use_container_width=True):
|
329 |
+
st.session_state.selected_exp = "Floating Egg"
|
330 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
331 |
|
332 |
+
with col2:
|
333 |
+
with st.container():
|
334 |
+
st.markdown('<div class="experiment-card">', unsafe_allow_html=True)
|
335 |
+
st.markdown("#### π Lemon Battery")
|
336 |
+
st.markdown("**Hypothesis:** A lemon can produce electricity to power a small LED.")
|
337 |
+
st.markdown("**Concept:** Chemical energy conversion to electrical energy.")
|
338 |
+
if st.button("Try This Experiment", key="lemon", use_container_width=True):
|
339 |
+
st.session_state.selected_exp = "Lemon Battery"
|
340 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
341 |
|
342 |
+
with st.container():
|
343 |
+
st.markdown('<div class="experiment-card">', unsafe_allow_html=True)
|
344 |
+
st.markdown("#### π Rainbow in a Glass")
|
345 |
+
st.markdown("**Hypothesis:** Different sugar solutions can form colorful layers in a glass.")
|
346 |
+
st.markdown("**Concept:** Density gradient formation.")
|
347 |
+
if st.button("Try This Experiment", key="rainbow", use_container_width=True):
|
348 |
+
st.session_state.selected_exp = "Custom Experiment"
|
349 |
+
st.session_state.custom_exp = "Rainbow in a Glass"
|
350 |
+
st.session_state.custom_hypo = "Different sugar solutions will form distinct layers based on their density."
|
351 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
352 |
+
|
353 |
+
# --- Lab Report Analyzer Section ---
|
354 |
+
else:
|
355 |
+
# --- File Upload ---
|
356 |
+
st.markdown('<p class="subheader">π€ Upload Your Lab Report</p>', unsafe_allow_html=True)
|
357 |
+
uploaded_file = st.file_uploader("Upload image (JPG, PNG) or PDF",
|
358 |
+
type=["jpg", "jpeg", "png", "pdf"],
|
359 |
+
label_visibility="collapsed")
|
360 |
+
|
361 |
+
lab_text = ""
|
362 |
+
if uploaded_file:
|
363 |
+
file_bytes = uploaded_file.read()
|
364 |
+
file_ext = uploaded_file.name.split(".")[-1].lower()
|
365 |
+
|
366 |
+
if file_ext == "pdf":
|
367 |
+
doc = fitz.open(stream=file_bytes, filetype="pdf")
|
368 |
+
for page in doc:
|
369 |
+
lab_text += page.get_text()
|
370 |
+
else:
|
371 |
+
image = Image.open(io.BytesIO(file_bytes))
|
372 |
+
lab_text = pytesseract.image_to_string(image)
|
373 |
+
|
374 |
+
# Allow text editing
|
375 |
+
st.markdown('<p class="subheader">βοΈ Extracted Text</p>', unsafe_allow_html=True)
|
376 |
+
st.caption("Review and edit the extracted text if needed before analysis")
|
377 |
+
lab_text = st.text_area("", lab_text, height=300, label_visibility="collapsed")
|
378 |
+
|
379 |
+
# --- AI Evaluation ---
|
380 |
+
if lab_text.strip() and api_key:
|
381 |
+
# -- AI Evaluation Prompt --
|
382 |
+
full_prompt = f"""You are a science teacher evaluating a student's lab report. Please provide a comprehensive analysis:
|
383 |
+
|
384 |
+
Lab Report:
|
385 |
+
{lab_text}
|
386 |
+
|
387 |
+
Evaluation Guidelines:
|
388 |
+
1. **Section Check**: Identify which of these sections are present and which are missing:
|
389 |
+
- Title
|
390 |
+
- Objective
|
391 |
+
- Hypothesis
|
392 |
+
- Materials
|
393 |
+
- Procedure
|
394 |
+
- Observations
|
395 |
+
- Results
|
396 |
+
- Conclusion
|
397 |
+
- References
|
398 |
+
|
399 |
+
2. **Completeness Score**:
|
400 |
+
- Assign a numerical score from 1-10 based on completeness
|
401 |
+
- Justify the score based on missing sections and content quality
|
402 |
+
|
403 |
+
3. **Improvement Tips**:
|
404 |
+
- For each missing section, explain why it's important
|
405 |
+
- Provide specific suggestions for improvement (e.g., "Try writing a more detailed observation section by including quantitative data")
|
406 |
+
- Highlight any sections that need more detail or clarity
|
407 |
+
|
408 |
+
4. **Structure Response**:
|
409 |
+
- Start with: "### Missing Sections:"
|
410 |
+
- Then: "### Completeness Score: X/10"
|
411 |
+
- Then: "### Improvement Tips:"
|
412 |
+
- Finally: "### Detailed Feedback:"
|
413 |
+
|
414 |
+
Be concise but thorough in your analysis.
|
415 |
+
"""
|
416 |
+
|
417 |
+
if st.button("π§ͺ Analyze Report", use_container_width=True):
|
418 |
+
with st.spinner("π Analyzing report with AI. This may take 20-30 seconds..."):
|
419 |
+
result = query_ai(full_prompt, api_key)
|
420 |
+
|
421 |
+
if result:
|
422 |
+
st.success("β
Analysis Complete!")
|
423 |
+
st.balloons()
|
424 |
+
|
425 |
+
# Extract score using regex
|
426 |
+
score_match = re.search(r"Completeness Score:\s*(\d+)/10", result, re.IGNORECASE)
|
427 |
+
score = int(score_match.group(1)) if score_match else None
|
428 |
+
|
429 |
+
# Display score in a card
|
430 |
+
if score is not None:
|
431 |
+
with st.container():
|
432 |
+
st.markdown('<div class="score-card">', unsafe_allow_html=True)
|
433 |
+
|
434 |
+
# Create columns for score visualization
|
435 |
+
col1, col2 = st.columns([1, 3])
|
436 |
+
|
437 |
+
with col1:
|
438 |
+
st.markdown(f"<h2 style='text-align: center; color: #28b463;'>{score}/10</h2>",
|
439 |
+
unsafe_allow_html=True)
|
440 |
+
st.markdown("<h4 style='text-align: center;'>Completeness Score</h4>",
|
441 |
+
unsafe_allow_html=True)
|
442 |
+
|
443 |
+
with col2:
|
444 |
+
# Create a color gradient based on score
|
445 |
+
if score >= 8:
|
446 |
+
color = "#28b463" # Green
|
447 |
+
elif score >= 5:
|
448 |
+
color = "#f39c12" # Orange
|
449 |
+
else:
|
450 |
+
color = "#e74c3c" # Red
|
451 |
+
|
452 |
+
# Display progress bar with styling
|
453 |
+
st.progress(score/10, text=f"{score*10}% complete")
|
454 |
+
st.markdown(
|
455 |
+
f"<style>"
|
456 |
+
f".stProgress > div > div > div {{"
|
457 |
+
f" background-color: {color} !important;"
|
458 |
+
f" border-radius: 10px;"
|
459 |
+
f"}}"
|
460 |
+
f"</style>",
|
461 |
+
unsafe_allow_html=True
|
462 |
+
)
|
463 |
+
|
464 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
465 |
+
|
466 |
+
# Display AI analysis with formatting
|
467 |
+
st.markdown("## π Analysis Results")
|
468 |
+
|
469 |
+
# Split sections for better display
|
470 |
+
sections = {
|
471 |
+
"Missing Sections": None,
|
472 |
+
"Improvement Tips": None,
|
473 |
+
"Detailed Feedback": None
|
474 |
+
}
|
475 |
+
|
476 |
+
current_section = None
|
477 |
+
for line in result.split('\n'):
|
478 |
+
if "### Missing Sections:" in line:
|
479 |
+
current_section = "Missing Sections"
|
480 |
+
sections[current_section] = []
|
481 |
+
elif "### Improvement Tips:" in line:
|
482 |
+
current_section = "Improvement Tips"
|
483 |
+
sections[current_section] = []
|
484 |
+
elif "### Detailed Feedback:" in line:
|
485 |
+
current_section = "Detailed Feedback"
|
486 |
+
sections[current_section] = []
|
487 |
+
elif current_section and line.strip():
|
488 |
+
sections[current_section].append(line)
|
489 |
+
|
490 |
+
# Display each section
|
491 |
+
if sections["Missing Sections"]:
|
492 |
+
st.markdown("### π Missing Sections")
|
493 |
+
missing_text = '\n'.join(sections["Missing Sections"])
|
494 |
+
st.markdown(f'<div class="highlight">{missing_text}</div>', unsafe_allow_html=True)
|
495 |
+
|
496 |
+
if sections["Improvement Tips"]:
|
497 |
+
st.markdown("### π‘ Improvement Tips")
|
498 |
+
tips_text = '\n'.join(sections["Improvement Tips"])
|
499 |
+
st.markdown(f'<div class="tip-box">{tips_text}</div>', unsafe_allow_html=True)
|
500 |
+
|
501 |
+
if sections["Detailed Feedback"]:
|
502 |
+
st.markdown("### π Detailed Feedback")
|
503 |
+
st.write('\n'.join(sections["Detailed Feedback"]))
|
504 |
+
|
505 |
+
# Show full AI response in expander
|
506 |
+
with st.expander("View Full AI Analysis"):
|
507 |
+
st.markdown(result)
|
508 |
+
|
509 |
+
# --- Question Answering Section ---
|
510 |
+
st.markdown("---")
|
511 |
+
st.markdown('<p class="subheader">β Ask About Your Report</p>', unsafe_allow_html=True)
|
512 |
+
|
513 |
+
col1, col2 = st.columns([3, 1])
|
514 |
+
with col1:
|
515 |
+
user_question = st.text_input("Ask a question about your lab report",
|
516 |
+
placeholder="e.g., How can I improve my hypothesis?")
|
517 |
+
with col2:
|
518 |
+
st.markdown("<div style='height: 28px;'></div>", unsafe_allow_html=True)
|
519 |
+
ask_button = st.button("π Ask Question", use_container_width=True)
|
520 |
+
|
521 |
+
if (ask_button or user_question) and user_question.strip():
|
522 |
+
with st.spinner("Thinking..."):
|
523 |
+
followup_prompt = f"""Lab Report:
|
524 |
+
{lab_text}
|
525 |
+
|
526 |
+
Question: {user_question}
|
527 |
+
|
528 |
+
Answer the question based on the lab report. If the question can't be answered from the report,
|
529 |
+
suggest what information the student should add to answer it.
|
530 |
+
"""
|
531 |
+
followup_response = query_ai(followup_prompt, api_key)
|
532 |
|
533 |
+
if followup_response:
|
534 |
+
st.markdown("### π¬ AI Response")
|
535 |
+
st.markdown(f'<div class="tip-box">{followup_response}</div>', unsafe_allow_html=True)
|
536 |
+
elif lab_text.strip() and not api_key:
|
537 |
+
st.error("β οΈ Please enter a valid OpenRouter API key to analyze your report")
|
538 |
+
else:
|
539 |
+
# Show sample report if no file uploaded
|
540 |
+
st.markdown("---")
|
541 |
+
st.markdown('<p class="subheader">π Sample Lab Report</p>', unsafe_allow_html=True)
|
542 |
+
st.markdown("""
|
543 |
+
**Title:** Effect of Temperature on Enzyme Activity
|
544 |
+
**Objective:** To investigate how temperature affects catalase enzyme activity
|
545 |
+
**Hypothesis:** Enzyme activity will increase with temperature up to 37Β°C, then decrease
|
546 |
+
**Materials:** Test tubes, hydrogen peroxide, liver extract, thermometer
|
547 |
+
**Procedure:**
|
548 |
+
1. Prepare test tubes at 5 different temperatures
|
549 |
+
2. Add equal amounts of hydrogen peroxide and liver extract
|
550 |
+
3. Measure oxygen production
|
551 |
+
**Observations:** More bubbles at 37Β°C compared to lower or higher temperatures
|
552 |
+
**Conclusion:** Enzyme activity peaks at body temperature
|
553 |
+
""")
|
554 |
|
555 |
+
st.info("π Upload your own lab report to get a personalized analysis!")
|
556 |
+
|
557 |
+
# --- Feedback Section ---
|
558 |
+
st.markdown("---")
|
559 |
+
with st.expander("π¬ Send Feedback"):
|
560 |
+
st.markdown("We'd love to hear your thoughts to improve this tool!")
|
561 |
+
feedback = st.text_area("What can we do better?")
|
562 |
+
if st.button("Submit Feedback", use_container_width=True):
|
563 |
+
st.success("Thank you for your feedback! We'll use it to improve the Science Lab Assistant.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|