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()