Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import sympy as sp | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| import pandas as pd | |
| from sympy import symbols, Eq | |
| from sklearn.linear_model import LinearRegression | |
| from sklearn.preprocessing import PolynomialFeatures | |
| from sklearn.pipeline import Pipeline | |
| from sklearn.metrics import r2_score | |
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as RLImage | |
| from reportlab.lib.styles import getSampleStyleSheet | |
| import os, ast | |
| # Variables | |
| x, y, z = symbols("x y z") | |
| # ---------- Utility functions ---------- | |
| def _safe_literal_list(s): | |
| try: | |
| val = ast.literal_eval(s) | |
| if isinstance(val, (list, tuple, np.ndarray)): | |
| return list(val) | |
| except Exception: | |
| pass | |
| return None | |
| def _parse_xy_from_text(text): | |
| text = text.strip() | |
| if "x=" in text and "y=" in text: | |
| try: | |
| xs = text.split("x=")[1].split("]")[0] + "]" | |
| ys = text.split("y=")[1].split("]")[0] + "]" | |
| X = _safe_literal_list(xs) | |
| Y = _safe_literal_list(ys) | |
| if X and Y and len(X) == len(Y): | |
| return np.array(X, dtype=float), np.array(Y, dtype=float) | |
| except Exception: | |
| pass | |
| if "(" in text and "," in text and ")" in text: | |
| try: | |
| pairs = [] | |
| for token in text.replace(";", " ").split(): | |
| if token.startswith("(") and token.endswith(")"): | |
| a, b = token[1:-1].split(",") | |
| pairs.append((float(a), float(b))) | |
| if pairs: | |
| arr = np.array(pairs, dtype=float) | |
| return arr[:,0], arr[:,1] | |
| except Exception: | |
| pass | |
| return None, None | |
| def _infer_xy_from_csv(df): | |
| lowered = {c.lower(): c for c in df.columns} | |
| if "x" in lowered and "y" in lowered: | |
| return df[lowered["x"]].to_numpy(dtype=float), df[lowered["y"]].to_numpy(dtype=float) | |
| numeric_cols = [c for c in df.columns if pd.api.types.is_numeric_dtype(df[c])] | |
| if len(numeric_cols) >= 2: | |
| return df[numeric_cols[0]].to_numpy(dtype=float), df[numeric_cols[1]].to_numpy(dtype=float) | |
| return None, None | |
| def _plot_save(fig_path="/tmp/plot.png"): | |
| plt.tight_layout() | |
| plt.savefig(fig_path, dpi=160, bbox_inches="tight") | |
| plt.close() | |
| return fig_path if os.path.exists(fig_path) else None | |
| # ---------- Symbolic solver ---------- | |
| def solve_symbolic_or_plot(user_input): | |
| reply, fig_path = "", None | |
| try: | |
| if "=" in user_input: | |
| left, right = user_input.split("=") | |
| eq = Eq(sp.sympify(left), sp.sympify(right)) | |
| sol = sp.solve(eq) | |
| steps = ( | |
| f"Equation: {eq}\n" | |
| f"Steps:\n" | |
| f"1) Move terms to one side.\n" | |
| f"2) Apply algebraic solving rules.\n" | |
| f"3) Solution = {sol}" | |
| ) | |
| reply = f"✅ Solution: {sol}\n\n{steps}" | |
| else: | |
| expr = sp.sympify(user_input) | |
| simplified = sp.simplify(expr) | |
| numeric_val = None | |
| try: | |
| numeric_val = float(simplified.evalf()) | |
| except Exception: | |
| pass | |
| steps = f"Expression: {expr}\n1) Simplify → {simplified}\n" | |
| if numeric_val is not None: | |
| steps += f"2) Evaluate numerically → {numeric_val}\n" | |
| reply = "✅ Done.\n\n" + steps | |
| if expr.has(x): | |
| f = sp.lambdify(x, expr, "numpy") | |
| xs = np.linspace(-10, 10, 400) | |
| ys = f(xs) | |
| plt.figure(figsize=(6,4)) | |
| plt.plot(xs, ys, label=str(expr)) | |
| plt.axhline(0, linewidth=0.7) | |
| plt.axvline(0, linewidth=0.7) | |
| plt.grid(True) | |
| plt.legend() | |
| plt.title("Graph") | |
| fig_path = _plot_save() | |
| except Exception as e: | |
| reply = f"❌ Could not process input.\nError: {e}" | |
| return reply, fig_path | |
| # ---------- Regression ---------- | |
| def run_regression(X, Y, degree=1): | |
| X = np.asarray(X).reshape(-1,1) if X.ndim == 1 else np.asarray(X) | |
| Y = np.asarray(Y).ravel() | |
| model = Pipeline([ | |
| ("poly", PolynomialFeatures(degree=degree, include_bias=False)), | |
| ("lin", LinearRegression()) | |
| ]) | |
| model.fit(X, Y) | |
| y_pred = model.predict(X) | |
| r2 = r2_score(Y, y_pred) | |
| equation = "Model learned." | |
| if X.shape[1] == 1: | |
| coefs = model.named_steps["lin"].coef_ | |
| intercept = model.named_steps["lin"].intercept_ | |
| terms = [] | |
| for i, c in enumerate(coefs, start=1): | |
| if abs(c) < 1e-12: continue | |
| if degree == 1: | |
| terms.append(f"{c:.4f}·x") | |
| else: | |
| terms.append(f"{c:.4f}·x^{i}") | |
| equation = " + ".join(terms) + f" + {intercept:.4f}" | |
| fig_path = None | |
| if X.shape[1] == 1: | |
| xs = np.linspace(float(np.min(X))-1, float(np.max(X))+1, 300).reshape(-1,1) | |
| ys = model.predict(xs) | |
| plt.figure(figsize=(6,4)) | |
| plt.scatter(X, Y, s=20, label="Data") | |
| plt.plot(xs, ys, label=f"Fit (deg={degree})") | |
| plt.grid(True) | |
| plt.legend() | |
| plt.title("Regression Fit") | |
| fig_path = _plot_save() | |
| report = f"✅ Regression (degree={degree})\nR² = {r2:.4f}\nEquation: {equation}" | |
| return report, fig_path | |
| # ---------- PDF Export ---------- | |
| def export_pdf(history, plot_path=None, filename="/tmp/math_report.pdf"): | |
| doc = SimpleDocTemplate(filename) | |
| styles = getSampleStyleSheet() | |
| flow = [Paragraph("📘 Math Chatbot Report", styles["Title"]), Spacer(1,12)] | |
| for q, a in history: | |
| flow.append(Paragraph(f"Q: {q}", styles["Heading3"])) | |
| flow.append(Paragraph(f"A: {a}", styles["Normal"])) | |
| flow.append(Spacer(1,12)) | |
| if plot_path and os.path.exists(plot_path): | |
| flow.append(RLImage(plot_path, width=400, height=300)) | |
| doc.build(flow) | |
| return filename | |
| # ---------- Chatbot logic ---------- | |
| HELP_TEXT = """\ | |
| Examples you can try: | |
| • Equation: x^2 - 5*x + 6 = 0 | |
| • Differentiation: diff(sin(x)*x, x) | |
| • Integration: integrate(x^2, x) | |
| • Limit: limit(sin(x)/x, x, 0) | |
| • Expression: (2+3*5)/7 | |
| • Function plot: sin(x) + x/3 | |
| • Regression: regress: x=[1,2,3]; y=[2,3,5]; degree=2 | |
| """ | |
| def bot(message, history, csv_file, degree, export): | |
| message_lower = (message or "").lower().strip() | |
| reply, fig_path = "", None | |
| if csv_file and "regress" in message_lower: | |
| try: | |
| df = pd.read_csv(csv_file.name) | |
| Xarr, Yarr = _infer_xy_from_csv(df) | |
| if Xarr is None: | |
| reply = "❌ CSV must have numeric x,y columns." | |
| else: | |
| reply, fig_path = run_regression(Xarr, Yarr, degree=int(degree)) | |
| except Exception as e: | |
| reply = f"❌ Error in regression from CSV.\n{e}" | |
| elif "regress" in message_lower: | |
| Xarr, Yarr = _parse_xy_from_text(message) | |
| if Xarr is None: | |
| reply = "❌ Could not parse x and y. Example: regress: x=[1,2,3]; y=[2,3,5]; degree=2" | |
| else: | |
| reply, fig_path = run_regression(Xarr, Yarr, degree=int(degree)) | |
| else: | |
| reply, fig_path = solve_symbolic_or_plot(message if message else "") | |
| if not message: | |
| reply = "Hi 👋\n" + HELP_TEXT | |
| history = history + [(message, reply)] | |
| pdf_path = None | |
| if export: | |
| pdf_path = export_pdf(history, fig_path) | |
| return history, history, (fig_path if fig_path else None), pdf_path | |
| # ---------- Gradio UI ---------- | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# 🧮 Interactive Math Chatbot — Solver • Steps • Graphs • Regression • PDF") | |
| gr.Markdown("Ask me any math problem. I can solve equations, calculus, plot functions, fit regression, and export a PDF report.\n\n" + HELP_TEXT) | |
| chatbot_ui = gr.Chatbot(height=380) | |
| msg = gr.Textbox(label="Type your math problem...") | |
| csv_in = gr.File(label="Optional CSV (x,y for regression)", file_types=[".csv"]) | |
| degree_in = gr.Slider(1, 6, value=1, step=1, label="Polynomial degree (for regression)") | |
| export_toggle = gr.Checkbox(label="Export PDF report?", value=False) | |
| solve_btn = gr.Button("Solve ✅") | |
| clear_btn = gr.Button("Clear Chat 🗑") | |
| pdf_out = gr.File(label="Download PDF", type="filepath") | |
| plot_out = gr.Image(label="Plot (if applicable)") | |
| state = gr.State([]) | |
| # زر Enter و Solve | |
| msg.submit(bot, [msg, state, csv_in, degree_in, export_toggle], [chatbot_ui, state, plot_out, pdf_out]) | |
| solve_btn.click(bot, [msg, state, csv_in, degree_in, export_toggle], [chatbot_ui, state, plot_out, pdf_out]) | |
| # زر مسح المحادثة | |
| def clear_chat(): | |
| return [], [], None, None | |
| clear_btn.click(clear_chat, outputs=[chatbot_ui, state, plot_out, pdf_out]) | |
| demo.launch() |