| |
| """ |
| نصّ البرنامج: مساعد نورا + نظام مهام موزعة + خيار لتشغيل كل ملفات بايثون (.py) الموجودة في نفس المجلد. |
| |
| |
| الميزات المضافة/المعدّلة |
| ======================== |
| 1. **تشغيل كل ملفات بايثون في نفس المجلد** يدويًا من واجهة Gradio أو تلقائيًا عند بدء التشغيل (يمكن التحكّم عبر متغيّر بيئة). |
| 2. جمع المخرجات (stdout / stderr) لكل ملف بشكل منفصل وإرجاعها/عرضها للمستخدم. |
| 3. استثناء الملف الحالي (الذي يحتوي الكود) تلقائيًا حتى لا يدخل في حلقة تشغيل ذاتي. |
| 4. استثناء مجلدات مثل `__pycache__` وأي ملف يبدأ بشرطة سفلية اختياريا. |
| 5. واجهة Gradio محدثة: تبويب جديد باسم "تشغيل جميع ملفات بايثون" يعرض قائمة الملفات + زر تشغيل + صندوق نتائج. |
| 6. دوال مساعدة نظيفة + تسجيل (logging) اختياري. |
| |
| |
| طريقة التحكم بالتشغيل التلقائي عند بدء البرنامج |
| ---------------------------------------------- |
| - اضبط متغيّر البيئة: `RUN_ALL_PY_ON_START=1` لتشغيلها عند الإقلاع. |
| - أو اتركه فارغًا/0 لتعطيل التشغيل التلقائي. |
| |
| |
| ملاحظات مهمة |
| ------------ |
| - التنفيذ يتم باستخدام `runpy.run_path()` بحيث يعمل كل ملف في مساحة أسماء مستقلة (قاموس). |
| - لا يتم عمل `import` مباشر؛ هذا يقلل تداخل الرموز ولكن *إذا كانت لديك ملفات تعتمد على الاستيراد النسبي* فربما تفضّل التحميل عبر `importlib`. (أنظر التعليق داخل الكود لاختيار وضع الاستيراد.) |
| - لو احتجت تمرير متغيرات/وسائط إلى هذه السكربتات الخارجية فسيتطلب ذلك بروتوكول إضافي (argparse مخفي، أو بيئة، أو ملف إعدادات مشترَك). |
| |
| |
| """ |
|
|
| import os |
| import io |
| import re |
| import json |
| import time |
| import glob |
| import runpy |
| import types |
| import logging |
| import threading |
| from contextlib import redirect_stdout, redirect_stderr |
|
|
| from flask import Flask, request, jsonify |
| import gradio as gr |
|
|
| from transformers import AutoTokenizer, AutoModelForCausalLM |
| from accelerate import init_empty_weights, load_checkpoint_and_dispatch |
|
|
| |
| from distributed_executor import DistributedExecutor |
| from your_tasks import * |
|
|
| |
| local_model_path = "./Mistral-7B-Instruct-v0.1" |
| offload_dir = "offload_dir" |
| history_path = "history.json" |
|
|
| |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") |
| logger = logging.getLogger(__name__) |
|
|
| |
| logger.info("Loading tokenizer: %s", local_model_path) |
| tokenizer = AutoTokenizer.from_pretrained(local_model_path) |
|
|
| logger.info("Initializing empty weights model shell…") |
| with init_empty_weights(): |
| model = AutoModelForCausalLM.from_pretrained(local_model_path) |
|
|
| logger.info("Dispatching model w/ CPU offload (accelerate)…") |
| model = load_checkpoint_and_dispatch( |
| model, |
| local_model_path, |
| device_map={"": "cpu"}, |
| offload_folder=offload_dir, |
| offload_state_dict=True, |
| ) |
|
|
| |
| if os.path.exists(history_path): |
| try: |
| with open(history_path, "r", encoding="utf-8") as f: |
| chat_history = json.load(f) |
| except Exception as e: |
| logger.warning("تعذّر تحميل سجل المحادثة (%s)، سيتم البدء بسجل فارغ.", e) |
| chat_history = [] |
| else: |
| chat_history = [] |
|
|
|
|
| def format_chat(history): |
| messages = [{"role": "system", "content": "أنت المساعدة نورا."}] |
| messages.extend(history) |
| return tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) |
|
|
|
|
| |
| def chat_with_nora(user_input): |
| global chat_history |
| chat_history.append({"role": "user", "content": user_input}) |
| prompt = format_chat(chat_history) |
| inputs = tokenizer(prompt, return_tensors="pt").to(model.device) |
| outputs = model.generate(**inputs, max_new_tokens=200, do_sample=True, temperature=0.7) |
| response = tokenizer.decode(outputs[0], skip_special_tokens=True) |
| |
| if "[/INST]" in response: |
| answer = response.split("[/INST]")[-1].strip() |
| else: |
| answer = response.strip() |
| chat_history.append({"role": "assistant", "content": answer}) |
| try: |
| with open(history_path, "w", encoding="utf-8") as f: |
| json.dump(chat_history, f, ensure_ascii=False, indent=2) |
| except Exception as e: |
| logger.error("Failed saving chat history: %s", e) |
| return answer |
|
|
|
|
| |
| executor = DistributedExecutor("my_shared_secret_123") |
| executor.peer_registry.register_service("node1", 7520, load=0.2) |
|
|
| |
| tasks = { |
| "ضرب المصفوفات": (matrix_multiply, 500), |
| "حساب الأعداد الأولية": (prime_calculation, 100000), |
| "معالجة البيانات": (data_processing, 10000), |
| "محاكاة معالجة الصور": (image_processing_emulation, 100), |
| "مهمة موزعة معقدة": (lambda x: x * x, 42), |
| } |
|
|
|
|
| def run_task(task_name): |
| if task_name in tasks: |
| func, arg = tasks[task_name] |
| start = time.time() |
| try: |
| result = func(arg) |
| status = "success" |
| except Exception as e: |
| result = f"ERROR: {e}" |
| status = "error" |
| duration = time.time() - start |
| return f"✅ الحالة: {status} |
| النتيجة: {str(result)[:200]}... |
| ⏱ الوقت: {duration:.2f} ثانية" |
| else: |
| return "❌ مهمة غير موجودة!" |
|
|
|
|
| |
| |
| |
|
|
| def discover_py_files(directory=None, *, exclude_self=True, exclude_private=True): |
| """اكتشف جميع ملفات .py في المجلد (غير متداخلة) مع خيارات استثناء.""" |
| directory = directory or os.path.dirname(os.path.abspath(__file__)) |
| self_path = os.path.abspath(__file__) |
| files = [] |
| for path in glob.glob(os.path.join(directory, "*.py")): |
| abs_path = os.path.abspath(path) |
| name = os.path.basename(abs_path) |
| if exclude_self and os.path.samefile(abs_path, self_path): |
| continue |
| if exclude_private and name.startswith("_"): |
| continue |
| files.append(abs_path) |
| return sorted(files) |
|
|
|
|
| def execute_single_py(path): |
| """شغّل ملف بايثون وأعد قاموسًا يحتوي stdout/stderr وزمن التنفيذ ونتيجة عودة محتملة.""" |
| start = time.time() |
| stdout_buf = io.StringIO() |
| stderr_buf = io.StringIO() |
| result_namespace = None |
| error = None |
| try: |
| with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf): |
| |
| result_namespace = runpy.run_path(path_name=path, run_name="__main__") |
| except Exception as e: |
| error = str(e) |
| duration = time.time() - start |
| return { |
| "file": path, |
| "stdout": stdout_buf.getvalue(), |
| "stderr": stderr_buf.getvalue(), |
| "error": error, |
| "globals": result_namespace if isinstance(result_namespace, dict) else {}, |
| "time": duration, |
| } |
|
|
|
|
| def execute_all_py_files(directory=None, *, exclude_self=True, exclude_private=True): |
| files = discover_py_files(directory, exclude_self=exclude_self, exclude_private=exclude_private) |
| results = [] |
| for fpath in files: |
| logger.info("Running: %s", fpath) |
| res = execute_single_py(fpath) |
| results.append(res) |
| return results |
|
|
|
|
| def summarize_execution_results(results): |
| """حوّل نتائج التنفيذ إلى نص قابل للعرض في Gradio.""" |
| lines = [] |
| for r in results: |
| lines.append("====================================") |
| lines.append(f"📄 الملف: {os.path.basename(r['file'])}") |
| lines.append(f"⏱ الوقت: {r['time']:.2f} ثانية") |
| if r["error"]: |
| lines.append(f"❌ خطأ: {r['error']}") |
| if r["stderr"].strip(): |
| lines.append("--- STDERR ---") |
| lines.append(r["stderr"].strip()) |
| if r["stdout"].strip(): |
| lines.append("--- STDOUT ---") |
| |
| out_preview = r["stdout"].strip() |
| if len(out_preview) > 500: |
| out_preview = out_preview[:500] + "... [تم الاقتصاص]" |
| lines.append(out_preview) |
| if not lines: |
| return "لا توجد ملفات للتشغيل."\ |
|
|
| return " |
| ".join(lines) |
|
|
|
|
| |
| app = Flask(__name__) |
|
|
|
|
| @app.route('/api/chat', methods=['POST']) |
| def api_chat(): |
| try: |
| data = request.get_json(force=True) |
| user_message = data.get('message', '').strip() |
|
|
| if not user_message: |
| return jsonify({'response': 'يرجى إدخال رسالة صالحة'}), 400 |
|
|
| response_text = chat_with_nora(user_message) |
| return jsonify({'response': response_text}) |
|
|
| except Exception as e: |
| logger.exception("/api/chat error") |
| return jsonify({'response': 'حدث خطأ داخلي', 'error': str(e)}), 500 |
|
|
|
|
| |
| @app.route('/api/run_all', methods=['POST']) |
| def api_run_all(): |
| try: |
| results = execute_all_py_files() |
| return jsonify({'results': results}) |
| except Exception as e: |
| logger.exception("/api/run_all error") |
| return jsonify({'error': str(e)}), 500 |
|
|
|
|
| |
|
|
| def launch_gradio(): |
| with gr.Blocks() as demo: |
| gr.Markdown("# 🤖 مساعد نورا + نظام المهام الموزعة + تشغيل سكربتات المجلد") |
|
|
| with gr.Tab("المحادثة مع نورا"): |
| chatbot = gr.Chatbot(type="messages") |
| msg = gr.Textbox() |
| send = gr.Button("إرسال") |
|
|
| def respond(message, chat_history_display): |
| answer = chat_with_nora(message) |
| chat_history_display.append({"role": "user", "content": message}) |
| chat_history_display.append({"role": "assistant", "content": answer}) |
| return "", chat_history_display |
|
|
| send.click(respond, [msg, chatbot], [msg, chatbot]) |
|
|
| with gr.Tab("تشغيل المهام"): |
| task_dropdown = gr.Dropdown(list(tasks.keys()), label="اختر مهمة") |
| run_button = gr.Button("تشغيل") |
| result_box = gr.Textbox(label="النتيجة", lines=6) |
|
|
| run_button.click(run_task, inputs=task_dropdown, outputs=result_box) |
|
|
| with gr.Tab("تشغيل جميع ملفات بايثون"): |
| gr.Markdown("### اكتشاف وتشغيل جميع ملفات .py في نفس مجلد هذا البرنامج") |
| refresh_btn = gr.Button("🔄 تحديث قائمة الملفات") |
| run_all_btn = gr.Button("🚀 تشغيل الكل") |
| files_box = gr.Textbox(label="الملفات المكتشفة", interactive=False) |
| run_all_result = gr.Textbox(label="نتائج التشغيل", lines=15) |
|
|
| def refresh_files(): |
| files = discover_py_files() |
| if not files: |
| return "لا توجد ملفات .py (باستثناء هذا الملف)." |
| return " |
| ".join(os.path.basename(f) for f in files) |
|
|
| def run_all_files_and_summarize(): |
| results = execute_all_py_files() |
| return summarize_execution_results(results) |
|
|
| refresh_btn.click(refresh_files, outputs=files_box) |
| run_all_btn.click(run_all_files_and_summarize, outputs=run_all_result) |
|
|
| demo.launch(server_name="0.0.0.0", server_port=7860) |
|
|
|
|
| |
| def startup_run_all_if_enabled(): |
| run_flag = os.environ.get("RUN_ALL_PY_ON_START", "0").strip() |
| if run_flag in {"1", "true", "True", "yes", "YES"}: |
| logger.info("RUN_ALL_PY_ON_START=1 -> Running all py files at startup…") |
| results = execute_all_py_files() |
| summary = summarize_execution_results(results) |
| logger.info("Startup batch complete: |
| %s", summary) |
| else: |
| logger.info("Startup batch NOT enabled (set RUN_ALL_PY_ON_START=1 to enable).") |
|
|
|
|
| if __name__ == '__main__': |
| |
| startup_run_all_if_enabled() |
|
|
| |
| threading.Thread(target=launch_gradio, daemon=True).start() |
|
|
| |
| app.run(host='0.0.0.0', port=5321, debug=True) |
|
|