Spaces:
Running on Zero
Running on Zero
| # tools_manager.py | |
| import os | |
| import re | |
| import json | |
| import math | |
| from datetime import datetime | |
| class ToolsManager: | |
| def __init__(self): | |
| self.base_dir = "tools_records" | |
| os.makedirs(self.base_dir, exist_ok=True) | |
| def safe_name(self, value): | |
| value = str(value or "").strip() | |
| value = re.sub(r"[^a-zA-Z0-9_\-ء-ي]+", "_", value) | |
| return value[:80] or "student" | |
| def calculate(self, expression): | |
| try: | |
| expression = str(expression or "").strip() | |
| if not expression: | |
| return { | |
| "success": False, | |
| "message": "اكتب العملية الحسابية أولًا." | |
| } | |
| expression = expression.replace("^", "**") | |
| allowed_names = { | |
| "sqrt": math.sqrt, | |
| "sin": math.sin, | |
| "cos": math.cos, | |
| "tan": math.tan, | |
| "pi": math.pi, | |
| "e": math.e, | |
| "abs": abs, | |
| "round": round, | |
| "pow": pow | |
| } | |
| result = eval(expression, {"__builtins__": {}}, allowed_names) | |
| return { | |
| "success": True, | |
| "message": f"النتيجة: {result}", | |
| "result": result | |
| } | |
| except Exception as e: | |
| return { | |
| "success": False, | |
| "message": f"تعذر الحساب: {e}" | |
| } | |
| def save_student_tool_note(self, identity, context, note, uploaded_file=None): | |
| try: | |
| identity = self.safe_name(identity) | |
| context = str(context or "عام").strip() | |
| note = str(note or "").strip() | |
| student_dir = os.path.join(self.base_dir, identity) | |
| os.makedirs(student_dir, exist_ok=True) | |
| stamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S") | |
| uploaded_path = None | |
| if uploaded_file is not None: | |
| try: | |
| uploaded_path = uploaded_file.name | |
| except Exception: | |
| uploaded_path = str(uploaded_file) | |
| payload = { | |
| "identity": identity, | |
| "context": context, | |
| "note": note, | |
| "uploaded_file": uploaded_path, | |
| "created_at": datetime.utcnow().isoformat(), | |
| "status": "saved_for_teacher_review" | |
| } | |
| path = os.path.join(student_dir, f"tool_note_{stamp}.json") | |
| with open(path, "w", encoding="utf-8") as f: | |
| json.dump(payload, f, ensure_ascii=False, indent=2) | |
| return { | |
| "success": True, | |
| "message": f"تم حفظ الملاحظة/السؤال لمراجعة الأستاذ: {path}", | |
| "path": path | |
| } | |
| except Exception as e: | |
| return { | |
| "success": False, | |
| "message": f"فشل حفظ الملاحظة: {e}" | |
| } | |
| def get_whiteboard_html(self): | |
| return """ | |
| <div style="direction:rtl; font-family:Arial; border:1px solid #cbd5e1; border-radius:14px; padding:10px; background:#f8fafc;"> | |
| <h3>السبورة التجريبية</h3> | |
| <div style="display:flex; flex-wrap:wrap; gap:8px; margin-bottom:8px;"> | |
| <button onclick="dtSetTool('pen')" type="button">قلم</button> | |
| <button onclick="dtSetTool('eraser')" type="button">محاية</button> | |
| <button onclick="dtClearBoard()" type="button">مسح</button> | |
| <button onclick="dtDownloadBoard()" type="button">حفظ صورة السبورة</button> | |
| <label> | |
| الحجم | |
| <input id="dt_size" type="range" min="1" max="14" value="4"> | |
| </label> | |
| <label> | |
| اللون | |
| <input id="dt_color" type="color" value="#000000"> | |
| </label> | |
| </div> | |
| <div style="height:420px; overflow:auto; border:2px solid #334155; border-radius:12px; background:#e2e8f0;"> | |
| <canvas id="dt_board" width="1200" height="1700" style="display:block; background:white; touch-action:none;"></canvas> | |
| </div> | |
| <p style="font-size:13px; color:#475569;"> | |
| هذه نسخة خفيفة آمنة. احفظ صورة السبورة، ثم يمكنك رفعها من أدواتي ليشاهدها الأستاذ. | |
| </p> | |
| </div> | |
| <script> | |
| (function () { | |
| const canvas = document.getElementById("dt_board"); | |
| if (!canvas) return; | |
| const ctx = canvas.getContext("2d"); | |
| let drawing = false; | |
| let tool = "pen"; | |
| let lastX = 0; | |
| let lastY = 0; | |
| function grid() { | |
| ctx.save(); | |
| ctx.strokeStyle = "#e5e7eb"; | |
| ctx.lineWidth = 1; | |
| for (let x = 0; x < canvas.width; x += 32) { | |
| ctx.beginPath(); | |
| ctx.moveTo(x, 0); | |
| ctx.lineTo(x, canvas.height); | |
| ctx.stroke(); | |
| } | |
| for (let y = 0; y < canvas.height; y += 32) { | |
| ctx.beginPath(); | |
| ctx.moveTo(0, y); | |
| ctx.lineTo(canvas.width, y); | |
| ctx.stroke(); | |
| } | |
| ctx.restore(); | |
| } | |
| window.dtSetTool = function (nextTool) { | |
| tool = nextTool; | |
| }; | |
| window.dtClearBoard = function () { | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| grid(); | |
| }; | |
| window.dtDownloadBoard = function () { | |
| const link = document.createElement("a"); | |
| link.download = "student_whiteboard.png"; | |
| link.href = canvas.toDataURL("image/png"); | |
| link.click(); | |
| }; | |
| function getPoint(event) { | |
| const rect = canvas.getBoundingClientRect(); | |
| return { | |
| x: event.clientX - rect.left, | |
| y: event.clientY - rect.top | |
| }; | |
| } | |
| function start(event) { | |
| event.preventDefault(); | |
| drawing = true; | |
| const p = getPoint(event); | |
| lastX = p.x; | |
| lastY = p.y; | |
| } | |
| function move(event) { | |
| if (!drawing) return; | |
| event.preventDefault(); | |
| const p = getPoint(event); | |
| const sizeInput = document.getElementById("dt_size"); | |
| const colorInput = document.getElementById("dt_color"); | |
| ctx.beginPath(); | |
| ctx.moveTo(lastX, lastY); | |
| ctx.lineTo(p.x, p.y); | |
| if (tool === "eraser") { | |
| ctx.strokeStyle = "#ffffff"; | |
| ctx.lineWidth = Math.max(18, parseInt(sizeInput.value, 10) * 3); | |
| } else { | |
| ctx.strokeStyle = colorInput.value; | |
| ctx.lineWidth = parseInt(sizeInput.value, 10); | |
| } | |
| ctx.lineCap = "round"; | |
| ctx.lineJoin = "round"; | |
| ctx.stroke(); | |
| lastX = p.x; | |
| lastY = p.y; | |
| } | |
| function stop() { | |
| drawing = false; | |
| } | |
| canvas.addEventListener("pointerdown", start); | |
| canvas.addEventListener("pointermove", move); | |
| window.addEventListener("pointerup", stop); | |
| window.addEventListener("pointercancel", stop); | |
| grid(); | |
| })(); | |
| </script> | |
| """.strip() |