Spaces:
Running
Running
Upload 15 files
Browse files- .gitattributes +1 -0
- .replit +62 -0
- app.py +327 -0
- auth.py +131 -0
- generated-icon.png +3 -0
- models.py +45 -0
- package-lock.json +0 -0
- package.json +28 -0
- plotter.py +79 -0
- pyproject.toml +20 -0
- replit.nix +6 -0
- requirements.txt +5 -0
- solver.py +182 -0
- style.css +158 -0
- utils.py +49 -0
- uv.lock +0 -0
.gitattributes
CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
generated-icon.png filter=lfs diff=lfs merge=lfs -text
|
.replit
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
modules = ["python-3.11", "nodejs-20", "postgresql-16"]
|
2 |
+
|
3 |
+
[nix]
|
4 |
+
channel = "stable-24_05"
|
5 |
+
|
6 |
+
[deployment]
|
7 |
+
deploymentTarget = "autoscale"
|
8 |
+
run = ["sh", "-c", "streamlit run main.py --server.port 5000"]
|
9 |
+
|
10 |
+
[workflows]
|
11 |
+
runButton = "Project"
|
12 |
+
|
13 |
+
[[workflows.workflow]]
|
14 |
+
name = "Project"
|
15 |
+
mode = "parallel"
|
16 |
+
author = "agent"
|
17 |
+
|
18 |
+
[[workflows.workflow.tasks]]
|
19 |
+
task = "workflow.run"
|
20 |
+
args = "Streamlit App"
|
21 |
+
|
22 |
+
[[workflows.workflow.tasks]]
|
23 |
+
task = "workflow.run"
|
24 |
+
args = "Backend Server"
|
25 |
+
|
26 |
+
[[workflows.workflow]]
|
27 |
+
name = "Streamlit App"
|
28 |
+
author = "agent"
|
29 |
+
|
30 |
+
[workflows.workflow.metadata]
|
31 |
+
agentRequireRestartOnSave = false
|
32 |
+
|
33 |
+
[[workflows.workflow.tasks]]
|
34 |
+
task = "packager.installForAll"
|
35 |
+
|
36 |
+
[[workflows.workflow.tasks]]
|
37 |
+
task = "shell.exec"
|
38 |
+
args = "streamlit run main.py --server.port 5000"
|
39 |
+
waitForPort = 5000
|
40 |
+
|
41 |
+
[[workflows.workflow]]
|
42 |
+
name = "Backend Server"
|
43 |
+
author = "agent"
|
44 |
+
|
45 |
+
[workflows.workflow.metadata]
|
46 |
+
agentRequireRestartOnSave = false
|
47 |
+
|
48 |
+
[[workflows.workflow.tasks]]
|
49 |
+
task = "packager.installForAll"
|
50 |
+
|
51 |
+
[[workflows.workflow.tasks]]
|
52 |
+
task = "shell.exec"
|
53 |
+
args = "PYTHONPATH=/home/runner/workspace python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000 --log-level debug"
|
54 |
+
waitForPort = 8000
|
55 |
+
|
56 |
+
[[ports]]
|
57 |
+
localPort = 5000
|
58 |
+
externalPort = 80
|
59 |
+
|
60 |
+
[[ports]]
|
61 |
+
localPort = 8000
|
62 |
+
externalPort = 8000
|
app.py
ADDED
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import sympy as sp
|
3 |
+
from solver import solve_equation, generate_steps
|
4 |
+
from plotter import plot_function
|
5 |
+
from utils import load_css, initialize_session_state
|
6 |
+
from auth import (
|
7 |
+
login_user, signup_user, is_logged_in, logout_user,
|
8 |
+
update_profile, get_user_profile, change_password
|
9 |
+
)
|
10 |
+
from models import History, SessionLocal, init_db
|
11 |
+
import base64
|
12 |
+
from io import BytesIO
|
13 |
+
|
14 |
+
# Initialize database
|
15 |
+
init_db()
|
16 |
+
|
17 |
+
def save_to_history(equation: str, solution: str):
|
18 |
+
"""Save equation and solution to user's history."""
|
19 |
+
if not is_logged_in():
|
20 |
+
return
|
21 |
+
|
22 |
+
db = SessionLocal()
|
23 |
+
try:
|
24 |
+
history = History(
|
25 |
+
user_id=st.session_state.user_id,
|
26 |
+
equation=equation,
|
27 |
+
solution=solution
|
28 |
+
)
|
29 |
+
db.add(history)
|
30 |
+
db.commit()
|
31 |
+
finally:
|
32 |
+
db.close()
|
33 |
+
|
34 |
+
def load_user_history():
|
35 |
+
"""Load user's solution history."""
|
36 |
+
if not is_logged_in():
|
37 |
+
return []
|
38 |
+
|
39 |
+
db = SessionLocal()
|
40 |
+
try:
|
41 |
+
return db.query(History).filter(
|
42 |
+
History.user_id == st.session_state.user_id
|
43 |
+
).order_by(History.created_at.desc()).all()
|
44 |
+
finally:
|
45 |
+
db.close()
|
46 |
+
|
47 |
+
def render_math_symbols():
|
48 |
+
"""Render mathematical symbols for input."""
|
49 |
+
st.markdown("### Mathematical Operators")
|
50 |
+
|
51 |
+
cols = st.columns(8)
|
52 |
+
all_symbols = {
|
53 |
+
# Basic arithmetic
|
54 |
+
'×': '*',
|
55 |
+
'÷': '/',
|
56 |
+
'^': '^',
|
57 |
+
'=': '=',
|
58 |
+
'(': '(',
|
59 |
+
')': ')',
|
60 |
+
|
61 |
+
# Functions and special operators
|
62 |
+
'√': '√',
|
63 |
+
'∫': '∫',
|
64 |
+
'd/dx': 'd/dx',
|
65 |
+
'!': '!',
|
66 |
+
'ℒ': 'L',
|
67 |
+
'∑': 'sum',
|
68 |
+
'∏': 'prod',
|
69 |
+
'|': '|',
|
70 |
+
|
71 |
+
# Constants
|
72 |
+
'π': 'pi',
|
73 |
+
'e': 'e',
|
74 |
+
'i': 'i',
|
75 |
+
'∞': 'oo',
|
76 |
+
|
77 |
+
# Trigonometric
|
78 |
+
'sin': 'sin(',
|
79 |
+
'cos': 'cos(',
|
80 |
+
'tan': 'tan(',
|
81 |
+
'csc': 'csc(',
|
82 |
+
'sec': 'sec(',
|
83 |
+
'cot': 'cot(',
|
84 |
+
|
85 |
+
# Inverse trigonometric
|
86 |
+
'sin⁻¹': 'sin⁻¹(',
|
87 |
+
'cos⁻¹': 'cos⁻¹(',
|
88 |
+
'tan⁻¹': 'tan⁻¹(',
|
89 |
+
|
90 |
+
# Other functions
|
91 |
+
'ln': 'ln(',
|
92 |
+
'log': 'log(',
|
93 |
+
'e^': 'e^',
|
94 |
+
'|x|': 'abs(',
|
95 |
+
}
|
96 |
+
|
97 |
+
for i, (label, symbol) in enumerate(all_symbols.items()):
|
98 |
+
col_idx = i % 8
|
99 |
+
if cols[col_idx].button(label, key=f"btn_{label}", help=f"Insert {label}"):
|
100 |
+
if 'equation' not in st.session_state:
|
101 |
+
st.session_state.equation = ''
|
102 |
+
st.session_state.equation += symbol
|
103 |
+
|
104 |
+
def render_profile_settings():
|
105 |
+
"""Render user profile settings page."""
|
106 |
+
st.title("Profile Settings")
|
107 |
+
|
108 |
+
user = get_user_profile(st.session_state.user_id)
|
109 |
+
|
110 |
+
# Profile photo upload
|
111 |
+
st.subheader("Profile Photo")
|
112 |
+
uploaded_file = st.file_uploader("Choose a profile photo", type=['jpg', 'jpeg', 'png'])
|
113 |
+
if uploaded_file:
|
114 |
+
# Convert to base64
|
115 |
+
bytes_data = uploaded_file.getvalue()
|
116 |
+
base64_image = base64.b64encode(bytes_data).decode()
|
117 |
+
success, message = update_profile(st.session_state.user_id, profile_photo=base64_image)
|
118 |
+
if success:
|
119 |
+
st.success("Profile photo updated!")
|
120 |
+
else:
|
121 |
+
st.error(message)
|
122 |
+
|
123 |
+
# Display current photo if exists
|
124 |
+
if user.profile_photo:
|
125 |
+
st.image(base64.b64decode(user.profile_photo), width=150)
|
126 |
+
|
127 |
+
# Personal Information
|
128 |
+
st.subheader("Personal Information")
|
129 |
+
with st.form("profile_form"):
|
130 |
+
full_name = st.text_input("Full Name", value=user.full_name or "")
|
131 |
+
email = st.text_input("Email", value=user.email or "")
|
132 |
+
school = st.text_input("School", value=user.school or "")
|
133 |
+
grade = st.text_input("Grade/Year", value=user.grade or "")
|
134 |
+
|
135 |
+
if st.form_submit_button("Update Profile"):
|
136 |
+
success, message = update_profile(
|
137 |
+
st.session_state.user_id,
|
138 |
+
full_name=full_name,
|
139 |
+
email=email,
|
140 |
+
school=school,
|
141 |
+
grade=grade
|
142 |
+
)
|
143 |
+
if success:
|
144 |
+
st.success("Profile updated successfully!")
|
145 |
+
else:
|
146 |
+
st.error(message)
|
147 |
+
|
148 |
+
# Password Change
|
149 |
+
st.subheader("Change Password")
|
150 |
+
with st.form("password_form"):
|
151 |
+
current_password = st.text_input("Current Password", type="password")
|
152 |
+
new_password = st.text_input("New Password", type="password")
|
153 |
+
confirm_password = st.text_input("Confirm New Password", type="password")
|
154 |
+
|
155 |
+
if st.form_submit_button("Change Password"):
|
156 |
+
if new_password != confirm_password:
|
157 |
+
st.error("New passwords do not match")
|
158 |
+
else:
|
159 |
+
success, message = change_password(
|
160 |
+
st.session_state.user_id,
|
161 |
+
current_password,
|
162 |
+
new_password
|
163 |
+
)
|
164 |
+
if success:
|
165 |
+
st.success("Password updated successfully!")
|
166 |
+
else:
|
167 |
+
st.error(message)
|
168 |
+
|
169 |
+
def render_auth_page():
|
170 |
+
"""Render login/signup page."""
|
171 |
+
st.title("Mathematical Problem Solver")
|
172 |
+
|
173 |
+
tab1, tab2 = st.tabs(["Login", "Sign Up"])
|
174 |
+
|
175 |
+
with tab1:
|
176 |
+
st.header("Login")
|
177 |
+
login_username = st.text_input("Username", key="login_username")
|
178 |
+
login_password = st.text_input("Password", type="password", key="login_password")
|
179 |
+
|
180 |
+
if st.button("Login"):
|
181 |
+
if login_user(login_username, login_password):
|
182 |
+
st.success("Successfully logged in!")
|
183 |
+
st.rerun()
|
184 |
+
else:
|
185 |
+
st.error("Invalid username or password")
|
186 |
+
|
187 |
+
with tab2:
|
188 |
+
st.header("Sign Up")
|
189 |
+
signup_username = st.text_input("Username", key="signup_username")
|
190 |
+
signup_password = st.text_input("Password", type="password", key="signup_password")
|
191 |
+
confirm_password = st.text_input("Confirm Password", type="password")
|
192 |
+
|
193 |
+
st.info("Password must be at least 8 characters long and contain at least one uppercase letter.")
|
194 |
+
|
195 |
+
if st.button("Sign Up"):
|
196 |
+
if signup_password != confirm_password:
|
197 |
+
st.error("Passwords do not match")
|
198 |
+
else:
|
199 |
+
success, message = signup_user(signup_username, signup_password)
|
200 |
+
if success:
|
201 |
+
st.success("Account created successfully!")
|
202 |
+
st.rerun()
|
203 |
+
else:
|
204 |
+
st.error(message)
|
205 |
+
|
206 |
+
def main():
|
207 |
+
# Initialize session state and load CSS
|
208 |
+
initialize_session_state()
|
209 |
+
load_css()
|
210 |
+
|
211 |
+
if not is_logged_in():
|
212 |
+
render_auth_page()
|
213 |
+
return
|
214 |
+
|
215 |
+
# Main app header with settings and logout buttons
|
216 |
+
col1, col2, col3 = st.columns([6, 1, 1])
|
217 |
+
with col1:
|
218 |
+
st.title("Mathematical Problem Solver")
|
219 |
+
with col2:
|
220 |
+
if st.button("Settings"):
|
221 |
+
st.session_state.show_settings = True
|
222 |
+
st.rerun()
|
223 |
+
with col3:
|
224 |
+
if st.button("Logout"):
|
225 |
+
logout_user()
|
226 |
+
st.rerun()
|
227 |
+
|
228 |
+
st.markdown(f"Welcome, {st.session_state.username}!")
|
229 |
+
|
230 |
+
# Show settings or main app
|
231 |
+
if st.session_state.get('show_settings', False):
|
232 |
+
render_profile_settings()
|
233 |
+
if st.button("Back to Calculator"):
|
234 |
+
st.session_state.show_settings = False
|
235 |
+
st.rerun()
|
236 |
+
else:
|
237 |
+
# Main calculator interface
|
238 |
+
# Sidebar for history
|
239 |
+
with st.sidebar:
|
240 |
+
st.header("Solution History")
|
241 |
+
history = load_user_history()
|
242 |
+
if history:
|
243 |
+
for idx, item in enumerate(history):
|
244 |
+
with st.expander(f"Problem {idx + 1}"):
|
245 |
+
st.write(f"Input: {item.equation}")
|
246 |
+
st.write(f"Solution: {item.solution}")
|
247 |
+
st.write(f"Date: {item.created_at.strftime('%Y-%m-%d %H:%M')}")
|
248 |
+
|
249 |
+
# Input method selection
|
250 |
+
input_method = st.radio(
|
251 |
+
"Choose input method:",
|
252 |
+
["Type equation/expression", "Use camera", "Example problems"]
|
253 |
+
)
|
254 |
+
|
255 |
+
if input_method == "Type equation/expression":
|
256 |
+
# Add math symbols
|
257 |
+
render_math_symbols()
|
258 |
+
|
259 |
+
# Equation input
|
260 |
+
if 'equation' not in st.session_state:
|
261 |
+
st.session_state.equation = ''
|
262 |
+
|
263 |
+
equation = st.text_input(
|
264 |
+
"Enter your equation or expression:",
|
265 |
+
value=st.session_state.equation,
|
266 |
+
help="""Examples:
|
267 |
+
- Equation: x^2 + 2x + 1 = 0
|
268 |
+
- Integration: ∫sin x
|
269 |
+
- Derivative: d/dx(x^2)
|
270 |
+
- Factorial: 5!
|
271 |
+
- Laplace: ℒ(t^2)"""
|
272 |
+
)
|
273 |
+
st.session_state.equation = equation
|
274 |
+
|
275 |
+
elif input_method == "Use camera":
|
276 |
+
st.info("📸 Camera input feature is coming soon! For now, please type your equation or choose from examples.")
|
277 |
+
equation = ""
|
278 |
+
|
279 |
+
else:
|
280 |
+
equation = st.selectbox(
|
281 |
+
"Select an example:",
|
282 |
+
[
|
283 |
+
"x^2 + 2x + 1 = 0",
|
284 |
+
"∫sin x",
|
285 |
+
"d/dx(x^3)",
|
286 |
+
"sin^2 x + cos^2 x",
|
287 |
+
"5!",
|
288 |
+
"2x + 3 = 7",
|
289 |
+
"e^x + 1 = 0",
|
290 |
+
"log(x) = 1"
|
291 |
+
]
|
292 |
+
)
|
293 |
+
|
294 |
+
if st.button("Solve"):
|
295 |
+
if equation:
|
296 |
+
try:
|
297 |
+
# Solve the equation or expression
|
298 |
+
solution = solve_equation(equation)
|
299 |
+
steps = generate_steps(equation)
|
300 |
+
|
301 |
+
# Display solution
|
302 |
+
st.markdown("### Solution")
|
303 |
+
st.write(solution)
|
304 |
+
|
305 |
+
# Display steps
|
306 |
+
st.markdown("### Step-by-step Solution")
|
307 |
+
for step in steps:
|
308 |
+
st.write(step)
|
309 |
+
|
310 |
+
# Plot the function if possible
|
311 |
+
try:
|
312 |
+
st.markdown("### Function Visualization")
|
313 |
+
fig = plot_function(equation)
|
314 |
+
st.plotly_chart(fig)
|
315 |
+
except Exception as plot_error:
|
316 |
+
st.info("Visualization not available for this type of expression.")
|
317 |
+
|
318 |
+
# Save to history
|
319 |
+
save_to_history(equation, solution)
|
320 |
+
|
321 |
+
except Exception as e:
|
322 |
+
st.error(f"Error: {str(e)}")
|
323 |
+
else:
|
324 |
+
st.warning("Please enter an equation or expression")
|
325 |
+
|
326 |
+
if __name__ == "__main__":
|
327 |
+
main()
|
auth.py
ADDED
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import hashlib
|
3 |
+
import re
|
4 |
+
from models import User, SessionLocal
|
5 |
+
|
6 |
+
def validate_password(password: str) -> tuple[bool, str]:
|
7 |
+
"""Validate password requirements."""
|
8 |
+
if len(password) < 8:
|
9 |
+
return False, "Password must be at least 8 characters long"
|
10 |
+
if not any(c.isupper() for c in password):
|
11 |
+
return False, "Password must contain at least one uppercase letter"
|
12 |
+
return True, ""
|
13 |
+
|
14 |
+
def hash_password(password: str) -> str:
|
15 |
+
"""Hash a password for storing."""
|
16 |
+
return hashlib.sha256(password.encode()).hexdigest()
|
17 |
+
|
18 |
+
def verify_password(stored_password: str, provided_password: str) -> bool:
|
19 |
+
"""Verify a stored password against one provided by user"""
|
20 |
+
return stored_password == hash_password(provided_password)
|
21 |
+
|
22 |
+
def login_user(username: str, password: str) -> bool:
|
23 |
+
"""Verify user credentials and log them in."""
|
24 |
+
db = SessionLocal()
|
25 |
+
try:
|
26 |
+
user = db.query(User).filter(User.username == username).first()
|
27 |
+
if user and verify_password(user.password, password):
|
28 |
+
st.session_state.user_id = user.id
|
29 |
+
st.session_state.username = user.username
|
30 |
+
return True
|
31 |
+
return False
|
32 |
+
finally:
|
33 |
+
db.close()
|
34 |
+
|
35 |
+
def signup_user(username: str, password: str) -> tuple[bool, str]:
|
36 |
+
"""Create a new user account."""
|
37 |
+
# Validate password
|
38 |
+
is_valid, message = validate_password(password)
|
39 |
+
if not is_valid:
|
40 |
+
return False, message
|
41 |
+
|
42 |
+
db = SessionLocal()
|
43 |
+
try:
|
44 |
+
# Check if username already exists
|
45 |
+
if db.query(User).filter(User.username == username).first():
|
46 |
+
return False, "Username already exists"
|
47 |
+
|
48 |
+
# Create new user
|
49 |
+
user = User(
|
50 |
+
username=username,
|
51 |
+
password=hash_password(password)
|
52 |
+
)
|
53 |
+
db.add(user)
|
54 |
+
db.commit()
|
55 |
+
|
56 |
+
# Log in the new user
|
57 |
+
st.session_state.user_id = user.id
|
58 |
+
st.session_state.username = user.username
|
59 |
+
return True, "Account created successfully"
|
60 |
+
except Exception as e:
|
61 |
+
db.rollback()
|
62 |
+
return False, str(e)
|
63 |
+
finally:
|
64 |
+
db.close()
|
65 |
+
|
66 |
+
def update_profile(user_id: int, **profile_data) -> tuple[bool, str]:
|
67 |
+
"""Update user profile information."""
|
68 |
+
db = SessionLocal()
|
69 |
+
try:
|
70 |
+
user = db.query(User).filter(User.id == user_id).first()
|
71 |
+
if not user:
|
72 |
+
return False, "User not found"
|
73 |
+
|
74 |
+
# Update user fields
|
75 |
+
for field, value in profile_data.items():
|
76 |
+
if hasattr(user, field):
|
77 |
+
setattr(user, field, value)
|
78 |
+
|
79 |
+
db.commit()
|
80 |
+
return True, "Profile updated successfully"
|
81 |
+
except Exception as e:
|
82 |
+
db.rollback()
|
83 |
+
return False, str(e)
|
84 |
+
finally:
|
85 |
+
db.close()
|
86 |
+
|
87 |
+
def get_user_profile(user_id: int) -> User:
|
88 |
+
"""Get user profile information."""
|
89 |
+
db = SessionLocal()
|
90 |
+
try:
|
91 |
+
return db.query(User).filter(User.id == user_id).first()
|
92 |
+
finally:
|
93 |
+
db.close()
|
94 |
+
|
95 |
+
def change_password(user_id: int, current_password: str, new_password: str) -> tuple[bool, str]:
|
96 |
+
"""Change user password."""
|
97 |
+
# Validate new password
|
98 |
+
is_valid, message = validate_password(new_password)
|
99 |
+
if not is_valid:
|
100 |
+
return False, message
|
101 |
+
|
102 |
+
db = SessionLocal()
|
103 |
+
try:
|
104 |
+
user = db.query(User).filter(User.id == user_id).first()
|
105 |
+
if not user:
|
106 |
+
return False, "User not found"
|
107 |
+
|
108 |
+
# Verify current password
|
109 |
+
if not verify_password(user.password, current_password):
|
110 |
+
return False, "Current password is incorrect"
|
111 |
+
|
112 |
+
# Update password
|
113 |
+
user.password = hash_password(new_password)
|
114 |
+
db.commit()
|
115 |
+
return True, "Password updated successfully"
|
116 |
+
except Exception as e:
|
117 |
+
db.rollback()
|
118 |
+
return False, str(e)
|
119 |
+
finally:
|
120 |
+
db.close()
|
121 |
+
|
122 |
+
def is_logged_in() -> bool:
|
123 |
+
"""Check if user is logged in."""
|
124 |
+
return 'user_id' in st.session_state
|
125 |
+
|
126 |
+
def logout_user():
|
127 |
+
"""Log out the current user."""
|
128 |
+
if 'user_id' in st.session_state:
|
129 |
+
del st.session_state.user_id
|
130 |
+
if 'username' in st.session_state:
|
131 |
+
del st.session_state.username
|
generated-icon.png
ADDED
![]() |
Git LFS Details
|
models.py
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
import sqlalchemy as sa
|
3 |
+
from sqlalchemy.ext.declarative import declarative_base
|
4 |
+
from sqlalchemy.orm import sessionmaker
|
5 |
+
from sqlalchemy import create_engine
|
6 |
+
import os
|
7 |
+
|
8 |
+
Base = declarative_base()
|
9 |
+
|
10 |
+
class User(Base):
|
11 |
+
__tablename__ = 'users'
|
12 |
+
|
13 |
+
id = sa.Column(sa.Integer, primary_key=True)
|
14 |
+
username = sa.Column(sa.String(50), unique=True, nullable=False)
|
15 |
+
password = sa.Column(sa.String(255), nullable=False)
|
16 |
+
full_name = sa.Column(sa.String(100))
|
17 |
+
email = sa.Column(sa.String(100))
|
18 |
+
school = sa.Column(sa.String(100))
|
19 |
+
grade = sa.Column(sa.String(20))
|
20 |
+
profile_photo = sa.Column(sa.Text) # Store photo as base64
|
21 |
+
created_at = sa.Column(sa.DateTime, default=datetime.utcnow)
|
22 |
+
updated_at = sa.Column(sa.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
23 |
+
|
24 |
+
class History(Base):
|
25 |
+
__tablename__ = 'history'
|
26 |
+
|
27 |
+
id = sa.Column(sa.Integer, primary_key=True)
|
28 |
+
user_id = sa.Column(sa.Integer, sa.ForeignKey('users.id'), nullable=False)
|
29 |
+
equation = sa.Column(sa.String(255), nullable=False)
|
30 |
+
solution = sa.Column(sa.Text, nullable=False)
|
31 |
+
created_at = sa.Column(sa.DateTime, default=datetime.utcnow)
|
32 |
+
|
33 |
+
# Database setup
|
34 |
+
engine = create_engine(os.environ['DATABASE_URL'])
|
35 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
36 |
+
|
37 |
+
def init_db():
|
38 |
+
Base.metadata.create_all(bind=engine)
|
39 |
+
|
40 |
+
def get_db():
|
41 |
+
db = SessionLocal()
|
42 |
+
try:
|
43 |
+
yield db
|
44 |
+
finally:
|
45 |
+
db.close()
|
package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
package.json
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "workspace",
|
3 |
+
"version": "1.0.0",
|
4 |
+
"main": "index.js",
|
5 |
+
"scripts": {
|
6 |
+
"test": "echo \"Error: no test specified\" && exit 1"
|
7 |
+
},
|
8 |
+
"keywords": [],
|
9 |
+
"author": "",
|
10 |
+
"license": "ISC",
|
11 |
+
"description": "",
|
12 |
+
"dependencies": {
|
13 |
+
"@emotion/react": "^11.14.0",
|
14 |
+
"@emotion/styled": "^11.14.0",
|
15 |
+
"@mui/icons-material": "^6.4.4",
|
16 |
+
"@mui/material": "^6.4.4",
|
17 |
+
"@types/react": "^19.0.8",
|
18 |
+
"@types/react-dom": "^19.0.3",
|
19 |
+
"@types/react-plotly.js": "^2.6.3",
|
20 |
+
"@types/react-router-dom": "^5.3.3",
|
21 |
+
"axios": "^1.7.9",
|
22 |
+
"react": "^19.0.0",
|
23 |
+
"react-dom": "^19.0.0",
|
24 |
+
"react-plotly.js": "^2.6.0",
|
25 |
+
"react-router-dom": "^7.1.5",
|
26 |
+
"typescript": "^5.7.3"
|
27 |
+
}
|
28 |
+
}
|
plotter.py
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import numpy as np
|
2 |
+
import plotly.graph_objects as go
|
3 |
+
from sympy import symbols, lambdify
|
4 |
+
from sympy.parsing.sympy_parser import parse_expr, standard_transformations, implicit_multiplication_application
|
5 |
+
from solver import preprocess_equation
|
6 |
+
|
7 |
+
def plot_function(input_str):
|
8 |
+
"""Create an interactive plot of the function."""
|
9 |
+
try:
|
10 |
+
# Preprocess input
|
11 |
+
processed_input = preprocess_equation(input_str)
|
12 |
+
|
13 |
+
# Handle different types of input
|
14 |
+
if '=' in processed_input:
|
15 |
+
# Equation: move everything to left side
|
16 |
+
left_side, right_side = [side.strip() for side in processed_input.split('=')]
|
17 |
+
transformations = standard_transformations + (implicit_multiplication_application,)
|
18 |
+
expr = parse_expr(left_side, transformations=transformations) - parse_expr(right_side, transformations=transformations)
|
19 |
+
elif input_str.startswith('∫'):
|
20 |
+
# Integration: plot the original function
|
21 |
+
expr = parse_expr(processed_input[1:].strip(), transformations=(standard_transformations + (implicit_multiplication_application,)))
|
22 |
+
elif input_str.startswith('d/dx'):
|
23 |
+
# Derivative: plot the original function
|
24 |
+
expr = parse_expr(processed_input[4:].strip(), transformations=(standard_transformations + (implicit_multiplication_application,)))
|
25 |
+
else:
|
26 |
+
# Regular expression
|
27 |
+
expr = parse_expr(processed_input, transformations=(standard_transformations + (implicit_multiplication_application,)))
|
28 |
+
|
29 |
+
# Create lambda function for numpy evaluation
|
30 |
+
x = symbols('x')
|
31 |
+
f = lambdify(x, expr, 'numpy')
|
32 |
+
|
33 |
+
# Generate x values
|
34 |
+
x_vals = np.linspace(-10, 10, 1000)
|
35 |
+
|
36 |
+
# Calculate y values
|
37 |
+
y_vals = f(x_vals)
|
38 |
+
|
39 |
+
# Create plot
|
40 |
+
fig = go.Figure()
|
41 |
+
|
42 |
+
# Add function curve
|
43 |
+
fig.add_trace(go.Scatter(
|
44 |
+
x=x_vals,
|
45 |
+
y=y_vals,
|
46 |
+
mode='lines',
|
47 |
+
name='f(x)',
|
48 |
+
line=dict(color='#FF4B4B', width=2)
|
49 |
+
))
|
50 |
+
|
51 |
+
# Add x-axis line
|
52 |
+
fig.add_trace(go.Scatter(
|
53 |
+
x=x_vals,
|
54 |
+
y=[0]*len(x_vals),
|
55 |
+
mode='lines',
|
56 |
+
name='x-axis',
|
57 |
+
line=dict(color='black', width=1)
|
58 |
+
))
|
59 |
+
|
60 |
+
# Update layout
|
61 |
+
fig.update_layout(
|
62 |
+
title='Function Visualization',
|
63 |
+
xaxis_title='x',
|
64 |
+
yaxis_title='y',
|
65 |
+
showlegend=True,
|
66 |
+
hovermode='x',
|
67 |
+
plot_bgcolor='white',
|
68 |
+
width=800,
|
69 |
+
height=500
|
70 |
+
)
|
71 |
+
|
72 |
+
# Update axes
|
73 |
+
fig.update_xaxes(zeroline=True, zerolinewidth=1, zerolinecolor='black', gridcolor='lightgray')
|
74 |
+
fig.update_yaxes(zeroline=True, zerolinewidth=1, zerolinecolor='black', gridcolor='lightgray')
|
75 |
+
|
76 |
+
return fig
|
77 |
+
|
78 |
+
except Exception as e:
|
79 |
+
raise Exception(f"Error creating plot: {str(e)}")
|
pyproject.toml
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[project]
|
2 |
+
name = "repl-nix-workspace"
|
3 |
+
version = "0.1.0"
|
4 |
+
description = "Add your description here"
|
5 |
+
requires-python = ">=3.11"
|
6 |
+
dependencies = [
|
7 |
+
"fastapi>=0.115.8",
|
8 |
+
"numpy>=2.2.3",
|
9 |
+
"openai>=1.63.0",
|
10 |
+
"passlib[bcrypt]>=1.7.4",
|
11 |
+
"plotly>=6.0.0",
|
12 |
+
"psycopg2-binary>=2.9.10",
|
13 |
+
"python-jose[cryptography]>=3.3.0",
|
14 |
+
"python-multipart>=0.0.20",
|
15 |
+
"sqlalchemy>=2.0.38",
|
16 |
+
"streamlit>=1.42.0",
|
17 |
+
"sympy>=1.13.3",
|
18 |
+
"twilio>=9.4.5",
|
19 |
+
"uvicorn>=0.34.0",
|
20 |
+
]
|
replit.nix
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{pkgs}: {
|
2 |
+
deps = [
|
3 |
+
pkgs.postgresql
|
4 |
+
pkgs.glibcLocales
|
5 |
+
];
|
6 |
+
}
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit
|
2 |
+
sympy
|
3 |
+
sqlalchemy
|
4 |
+
plotly
|
5 |
+
pandas
|
solver.py
ADDED
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sympy as sp
|
2 |
+
from sympy.parsing.sympy_parser import parse_expr, standard_transformations, implicit_multiplication_application
|
3 |
+
from sympy.solvers import solve
|
4 |
+
from sympy import integrate, diff, simplify, expand, log, exp, sin, cos, tan, asin, acos, atan, Symbol, factorial, laplace_transform
|
5 |
+
|
6 |
+
def format_expression(expr):
|
7 |
+
"""Format expression to make it more readable."""
|
8 |
+
# Convert string representation to a more readable format
|
9 |
+
str_expr = str(expr)
|
10 |
+
replacements = {
|
11 |
+
'**': '^',
|
12 |
+
'*x': 'x',
|
13 |
+
'exp': 'e^',
|
14 |
+
'sqrt': '√',
|
15 |
+
'factorial': '!'
|
16 |
+
}
|
17 |
+
for old, new in replacements.items():
|
18 |
+
str_expr = str_expr.replace(old, new)
|
19 |
+
return str_expr
|
20 |
+
|
21 |
+
def preprocess_equation(equation_str):
|
22 |
+
"""Convert user-friendly equation format to SymPy format."""
|
23 |
+
try:
|
24 |
+
# Replace common mathematical notations
|
25 |
+
replacements = {
|
26 |
+
'^': '**',
|
27 |
+
'sin⁻¹': 'asin',
|
28 |
+
'cos⁻¹': 'acos',
|
29 |
+
'tan⁻¹': 'atan',
|
30 |
+
'e^': 'exp',
|
31 |
+
'ln': 'log',
|
32 |
+
'!': 'factorial',
|
33 |
+
}
|
34 |
+
for old, new in replacements.items():
|
35 |
+
equation_str = equation_str.replace(old, new)
|
36 |
+
|
37 |
+
# Handle exponential expressions
|
38 |
+
if 'exp' in equation_str:
|
39 |
+
parts = equation_str.split('exp')
|
40 |
+
for i in range(1, len(parts)):
|
41 |
+
if parts[i] and parts[i][0] != '(':
|
42 |
+
parts[i] = '(' + parts[i]
|
43 |
+
if '=' in parts[i]:
|
44 |
+
exp_part, rest = parts[i].split('=', 1)
|
45 |
+
parts[i] = exp_part + ')=' + rest
|
46 |
+
else:
|
47 |
+
parts[i] = parts[i] + ')'
|
48 |
+
equation_str = 'exp'.join(parts)
|
49 |
+
|
50 |
+
# Add multiplication symbol where needed
|
51 |
+
processed = ''
|
52 |
+
i = 0
|
53 |
+
while i < len(equation_str):
|
54 |
+
if i + 1 < len(equation_str):
|
55 |
+
if equation_str[i].isdigit() and equation_str[i+1] == 'x':
|
56 |
+
processed += equation_str[i] + '*'
|
57 |
+
i += 1
|
58 |
+
continue
|
59 |
+
processed += equation_str[i]
|
60 |
+
i += 1
|
61 |
+
|
62 |
+
return processed
|
63 |
+
except Exception as e:
|
64 |
+
raise Exception(f"Error in equation format: {str(e)}")
|
65 |
+
|
66 |
+
def process_expression(expr_str):
|
67 |
+
"""Process mathematical expressions without equations."""
|
68 |
+
try:
|
69 |
+
# Preprocess the expression
|
70 |
+
processed_expr = preprocess_equation(expr_str)
|
71 |
+
x = Symbol('x')
|
72 |
+
|
73 |
+
# Check for special operations
|
74 |
+
if expr_str.startswith('∫'): # Integration
|
75 |
+
expr_to_integrate = processed_expr[1:].strip()
|
76 |
+
expr = parse_expr(expr_to_integrate, transformations=(standard_transformations + (implicit_multiplication_application,)))
|
77 |
+
result = integrate(expr, x)
|
78 |
+
return f"∫{format_expression(expr)} = {format_expression(result)}"
|
79 |
+
|
80 |
+
elif expr_str.startswith('d/dx'): # Differentiation
|
81 |
+
expr_to_diff = processed_expr[4:].strip()
|
82 |
+
if expr_to_diff.startswith('(') and expr_to_diff.endswith(')'):
|
83 |
+
expr_to_diff = expr_to_diff[1:-1]
|
84 |
+
expr = parse_expr(expr_to_diff, transformations=(standard_transformations + (implicit_multiplication_application,)))
|
85 |
+
result = diff(expr, x)
|
86 |
+
return f"d/dx({format_expression(expr)}) = {format_expression(result)}"
|
87 |
+
|
88 |
+
elif 'factorial' in processed_expr: # Factorial
|
89 |
+
expr = parse_expr(processed_expr, transformations=(standard_transformations + (implicit_multiplication_application,)))
|
90 |
+
result = expr.doit()
|
91 |
+
return f"{format_expression(expr)} = {format_expression(result)}"
|
92 |
+
|
93 |
+
else: # Regular expression simplification
|
94 |
+
expr = parse_expr(processed_expr, transformations=(standard_transformations + (implicit_multiplication_application,)))
|
95 |
+
simplified = simplify(expr)
|
96 |
+
expanded = expand(simplified)
|
97 |
+
return f"Simplified: {format_expression(simplified)}\nExpanded: {format_expression(expanded)}"
|
98 |
+
|
99 |
+
except Exception as e:
|
100 |
+
raise Exception(f"Error processing expression: {str(e)}")
|
101 |
+
|
102 |
+
def solve_equation(equation_str):
|
103 |
+
"""Solve the given equation and return the solution."""
|
104 |
+
try:
|
105 |
+
if '=' not in equation_str:
|
106 |
+
return process_expression(equation_str)
|
107 |
+
|
108 |
+
# Preprocess equation
|
109 |
+
equation_str = preprocess_equation(equation_str)
|
110 |
+
|
111 |
+
# Split equation into left and right parts
|
112 |
+
left_side, right_side = [side.strip() for side in equation_str.split('=')]
|
113 |
+
|
114 |
+
# Parse both sides with implicit multiplication
|
115 |
+
transformations = standard_transformations + (implicit_multiplication_application,)
|
116 |
+
left_expr = parse_expr(left_side, transformations=transformations)
|
117 |
+
right_expr = parse_expr(right_side, transformations=transformations)
|
118 |
+
equation = left_expr - right_expr
|
119 |
+
|
120 |
+
# Solve the equation
|
121 |
+
x = Symbol('x')
|
122 |
+
solution = solve(equation, x)
|
123 |
+
|
124 |
+
# Format solution
|
125 |
+
if len(solution) == 0:
|
126 |
+
return "No solution exists"
|
127 |
+
elif len(solution) == 1:
|
128 |
+
return f"x = {format_expression(solution[0])}"
|
129 |
+
else:
|
130 |
+
return "x = " + ", ".join([format_expression(sol) for sol in solution])
|
131 |
+
|
132 |
+
except Exception as e:
|
133 |
+
raise Exception(f"Invalid equation format: {str(e)}")
|
134 |
+
|
135 |
+
def generate_steps(equation_str):
|
136 |
+
"""Generate step-by-step solution for the equation or expression."""
|
137 |
+
steps = []
|
138 |
+
try:
|
139 |
+
if '=' not in equation_str:
|
140 |
+
steps.append(f"1. Original expression: {equation_str}")
|
141 |
+
result = process_expression(equation_str)
|
142 |
+
steps.append(f"2. Result: {result}")
|
143 |
+
return steps
|
144 |
+
|
145 |
+
# Preprocess equation
|
146 |
+
processed_eq = preprocess_equation(equation_str)
|
147 |
+
|
148 |
+
# Split equation into left and right parts
|
149 |
+
left_side, right_side = [side.strip() for side in processed_eq.split('=')]
|
150 |
+
|
151 |
+
# Parse expressions with implicit multiplication
|
152 |
+
transformations = standard_transformations + (implicit_multiplication_application,)
|
153 |
+
left_expr = parse_expr(left_side, transformations=transformations)
|
154 |
+
right_expr = parse_expr(right_side, transformations=transformations)
|
155 |
+
|
156 |
+
# Step 1: Show original equation
|
157 |
+
steps.append(f"1. Original equation: {format_expression(left_expr)} = {format_expression(right_expr)}")
|
158 |
+
|
159 |
+
# Step 2: Move all terms to left side
|
160 |
+
equation = left_expr - right_expr
|
161 |
+
steps.append(f"2. Move all terms to left side: {format_expression(equation)} = 0")
|
162 |
+
|
163 |
+
# Step 3: Factor if possible
|
164 |
+
factored = sp.factor(equation)
|
165 |
+
if factored != equation:
|
166 |
+
steps.append(f"3. Factor the equation: {format_expression(factored)} = 0")
|
167 |
+
|
168 |
+
# Step 4: Solve
|
169 |
+
x = Symbol('x')
|
170 |
+
solution = solve(equation, x)
|
171 |
+
steps.append(f"4. Solve for x: x = {', '.join([format_expression(sol) for sol in solution])}")
|
172 |
+
|
173 |
+
# Step 5: Verify solutions
|
174 |
+
steps.append("5. Verify solutions:")
|
175 |
+
for sol in solution:
|
176 |
+
result = equation.subs(x, sol)
|
177 |
+
steps.append(f" When x = {format_expression(sol)}, equation equals {format_expression(result)}")
|
178 |
+
|
179 |
+
return steps
|
180 |
+
|
181 |
+
except Exception as e:
|
182 |
+
raise Exception(f"Error generating steps: {str(e)}")
|
style.css
ADDED
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Custom styles for the application */
|
2 |
+
body {
|
3 |
+
font-family: 'Arial', sans-serif;
|
4 |
+
color: #FFFFFF;
|
5 |
+
background-color: #181818;
|
6 |
+
}
|
7 |
+
|
8 |
+
/* Button styling */
|
9 |
+
.stButton>button {
|
10 |
+
min-width: 40px !important;
|
11 |
+
height: 30px !important;
|
12 |
+
padding: 2px 8px !important;
|
13 |
+
margin: 2px !important;
|
14 |
+
background-color: #2D5BE3;
|
15 |
+
color: white;
|
16 |
+
border: none;
|
17 |
+
border-radius: 4px;
|
18 |
+
cursor: pointer;
|
19 |
+
transition: all 0.3s ease;
|
20 |
+
font-size: 14px;
|
21 |
+
font-weight: bold;
|
22 |
+
text-shadow: 0 1px 2px rgba(0,0,0,0.2);
|
23 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
24 |
+
}
|
25 |
+
|
26 |
+
.stButton>button:hover {
|
27 |
+
background-color: #3DDC84;
|
28 |
+
transform: translateY(-1px);
|
29 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
30 |
+
}
|
31 |
+
|
32 |
+
/* Input field styling */
|
33 |
+
.stTextInput>div>div>input {
|
34 |
+
border-radius: 5px;
|
35 |
+
border: 2px solid #2D5BE3;
|
36 |
+
padding: 0.5rem;
|
37 |
+
background-color: #242424;
|
38 |
+
color: #FFFFFF;
|
39 |
+
font-weight: 500;
|
40 |
+
}
|
41 |
+
|
42 |
+
.stTextInput>div>div>input:focus {
|
43 |
+
border-color: #FFDD44;
|
44 |
+
box-shadow: 0 0 0 2px rgba(255, 221, 68, 0.2);
|
45 |
+
}
|
46 |
+
|
47 |
+
/* Sidebar styling */
|
48 |
+
.sidebar .sidebar-content {
|
49 |
+
background-color: #242424;
|
50 |
+
padding: 1rem;
|
51 |
+
border-right: 2px solid #2D5BE3;
|
52 |
+
}
|
53 |
+
|
54 |
+
/* Solution display styling */
|
55 |
+
.stExpander {
|
56 |
+
background-color: #242424;
|
57 |
+
border-radius: 8px;
|
58 |
+
margin-bottom: 10px;
|
59 |
+
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
|
60 |
+
border: 1px solid #3DDC84;
|
61 |
+
}
|
62 |
+
|
63 |
+
/* Plot container styling */
|
64 |
+
.plot-container {
|
65 |
+
background-color: #242424;
|
66 |
+
border-radius: 8px;
|
67 |
+
padding: 1rem;
|
68 |
+
margin: 1rem 0;
|
69 |
+
border: 1px solid #2D5BE3;
|
70 |
+
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
|
71 |
+
}
|
72 |
+
|
73 |
+
/* Solution steps styling */
|
74 |
+
.solution-step {
|
75 |
+
background-color: #242424;
|
76 |
+
padding: 1rem;
|
77 |
+
margin: 0.75rem 0;
|
78 |
+
border-radius: 6px;
|
79 |
+
border-left: 4px solid #3DDC84;
|
80 |
+
}
|
81 |
+
|
82 |
+
/* Mathematical symbols grid */
|
83 |
+
.math-symbols-grid {
|
84 |
+
display: grid;
|
85 |
+
grid-template-columns: repeat(8, 1fr);
|
86 |
+
gap: 4px;
|
87 |
+
margin: 10px 0;
|
88 |
+
}
|
89 |
+
|
90 |
+
/* Headers styling */
|
91 |
+
h1, h2, h3 {
|
92 |
+
color: #FFFFFF;
|
93 |
+
font-weight: bold;
|
94 |
+
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
95 |
+
}
|
96 |
+
|
97 |
+
/* Interactive elements */
|
98 |
+
.stSelectbox select,
|
99 |
+
.stMultiSelect select {
|
100 |
+
background-color: #242424;
|
101 |
+
color: #FFFFFF;
|
102 |
+
border: 2px solid #2D5BE3;
|
103 |
+
}
|
104 |
+
|
105 |
+
/* Radio buttons and checkboxes */
|
106 |
+
.stRadio > div {
|
107 |
+
border-radius: 6px;
|
108 |
+
padding: 0.5rem;
|
109 |
+
background-color: #242424;
|
110 |
+
}
|
111 |
+
|
112 |
+
.stRadio label {
|
113 |
+
color: #FFFFFF !important;
|
114 |
+
}
|
115 |
+
|
116 |
+
.stRadio label:hover {
|
117 |
+
color: #FFDD44 !important;
|
118 |
+
}
|
119 |
+
|
120 |
+
/* Tabs */
|
121 |
+
.stTabs {
|
122 |
+
background-color: #242424;
|
123 |
+
border-radius: 8px;
|
124 |
+
padding: 1rem;
|
125 |
+
margin: 1rem 0;
|
126 |
+
}
|
127 |
+
|
128 |
+
/* Success/Info messages */
|
129 |
+
.stSuccess, .stInfo {
|
130 |
+
background-color: #3DDC84;
|
131 |
+
color: #181818;
|
132 |
+
font-weight: bold;
|
133 |
+
border-radius: 6px;
|
134 |
+
padding: 0.75rem;
|
135 |
+
}
|
136 |
+
|
137 |
+
/* Error messages */
|
138 |
+
.stError {
|
139 |
+
background-color: #FF4444;
|
140 |
+
color: #FFFFFF;
|
141 |
+
font-weight: bold;
|
142 |
+
border-radius: 6px;
|
143 |
+
padding: 0.75rem;
|
144 |
+
}
|
145 |
+
|
146 |
+
/* Responsive design */
|
147 |
+
@media (max-width: 768px) {
|
148 |
+
.stButton>button {
|
149 |
+
min-width: 30px !important;
|
150 |
+
height: 25px !important;
|
151 |
+
padding: 1px 6px !important;
|
152 |
+
font-size: 12px;
|
153 |
+
}
|
154 |
+
|
155 |
+
.stTextInput>div>div>input {
|
156 |
+
font-size: 16px;
|
157 |
+
}
|
158 |
+
}
|
utils.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
|
3 |
+
def initialize_session_state():
|
4 |
+
"""Initialize session state variables."""
|
5 |
+
if 'history' not in st.session_state:
|
6 |
+
st.session_state.history = []
|
7 |
+
|
8 |
+
def load_css():
|
9 |
+
"""Load custom CSS styles."""
|
10 |
+
st.markdown("""
|
11 |
+
<style>
|
12 |
+
.stButton>button {
|
13 |
+
width: 100%;
|
14 |
+
background-color: #FF4B4B;
|
15 |
+
color: white;
|
16 |
+
}
|
17 |
+
|
18 |
+
.stTextInput>div>div>input {
|
19 |
+
border-radius: 5px;
|
20 |
+
}
|
21 |
+
|
22 |
+
.stMarkdown {
|
23 |
+
font-family: 'Arial', sans-serif;
|
24 |
+
}
|
25 |
+
|
26 |
+
h1, h2, h3 {
|
27 |
+
color: #262730;
|
28 |
+
}
|
29 |
+
|
30 |
+
.sidebar .sidebar-content {
|
31 |
+
background-color: #F0F2F6;
|
32 |
+
}
|
33 |
+
|
34 |
+
.stExpander {
|
35 |
+
background-color: white;
|
36 |
+
border-radius: 5px;
|
37 |
+
margin-bottom: 10px;
|
38 |
+
}
|
39 |
+
|
40 |
+
.stRadio > div {
|
41 |
+
display: flex;
|
42 |
+
gap: 1rem;
|
43 |
+
}
|
44 |
+
|
45 |
+
.stSelectbox {
|
46 |
+
margin-bottom: 1rem;
|
47 |
+
}
|
48 |
+
</style>
|
49 |
+
""", unsafe_allow_html=True)
|
uv.lock
ADDED
The diff for this file is too large to render.
See raw diff
|
|