osamabyc commited on
Commit
1e68797
·
verified ·
1 Parent(s): 874332e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +224 -20
app.py CHANGED
@@ -1,44 +1,99 @@
1
  # -*- coding: utf-8 -*-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import os
 
 
3
  import json
4
  import time
 
 
 
 
5
  import threading
 
 
6
  from flask import Flask, request, jsonify
7
  import gradio as gr
 
8
  from transformers import AutoTokenizer, AutoModelForCausalLM
9
  from accelerate import init_empty_weights, load_checkpoint_and_dispatch
 
 
10
  from distributed_executor import DistributedExecutor
11
- from your_tasks import *
12
 
13
  # ================= إعدادات الموديل =================
14
  local_model_path = "./Mistral-7B-Instruct-v0.1"
15
  offload_dir = "offload_dir"
16
  history_path = "history.json"
17
 
18
- # تحميل التوكنيزر والموديل
 
 
 
 
 
19
  tokenizer = AutoTokenizer.from_pretrained(local_model_path)
 
 
20
  with init_empty_weights():
21
  model = AutoModelForCausalLM.from_pretrained(local_model_path)
 
 
22
  model = load_checkpoint_and_dispatch(
23
  model,
24
  local_model_path,
25
- device_map={"": "cpu"},
26
  offload_folder=offload_dir,
27
- offload_state_dict=True
28
  )
29
 
30
- # تحميل سجل المحادثة
31
  if os.path.exists(history_path):
32
- with open(history_path, "r", encoding="utf-8") as f:
33
- chat_history = json.load(f)
 
 
 
 
34
  else:
35
  chat_history = []
36
 
 
37
  def format_chat(history):
38
  messages = [{"role": "system", "content": "أنت المساعدة نورا."}]
39
  messages.extend(history)
40
  return tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
41
 
 
42
  # ================= دالة الرد من نورا =================
43
  def chat_with_nora(user_input):
44
  global chat_history
@@ -47,37 +102,136 @@ def chat_with_nora(user_input):
47
  inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
48
  outputs = model.generate(**inputs, max_new_tokens=200, do_sample=True, temperature=0.7)
49
  response = tokenizer.decode(outputs[0], skip_special_tokens=True)
50
- answer = response.split("[/INST]")[-1].strip()
 
 
 
 
51
  chat_history.append({"role": "assistant", "content": answer})
52
- with open(history_path, "w", encoding="utf-8") as f:
53
- json.dump(chat_history, f, ensure_ascii=False, indent=2)
 
 
 
54
  return answer
55
 
 
56
  # ================= نظام توزيع المهام =================
57
  executor = DistributedExecutor("my_shared_secret_123")
58
  executor.peer_registry.register_service("node1", 7520, load=0.2)
59
 
 
60
  tasks = {
61
  "ضرب المصفوفات": (matrix_multiply, 500),
62
  "حساب الأعداد الأولية": (prime_calculation, 100000),
63
  "معالجة البيانات": (data_processing, 10000),
64
  "محاكاة معالجة الصور": (image_processing_emulation, 100),
65
- "مهمة موزعة معقدة": (lambda x: x * x, 42)
66
  }
67
 
 
68
  def run_task(task_name):
69
  if task_name in tasks:
70
  func, arg = tasks[task_name]
71
  start = time.time()
72
- result = func(arg)
 
 
 
 
 
73
  duration = time.time() - start
74
- return f"✅ النتيجة: {str(result)[:200]}...\n⏱ الوقت: {duration:.2f} ثانية"
 
 
75
  else:
76
  return "❌ مهمة غير موجودة!"
77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  # ================= خادم Flask API =================
79
  app = Flask(__name__)
80
 
 
81
  @app.route('/api/chat', methods=['POST'])
82
  def api_chat():
83
  try:
@@ -90,13 +244,27 @@ def api_chat():
90
  response_text = chat_with_nora(user_message)
91
  return jsonify({'response': response_text})
92
 
93
- except Exception as e:
 
94
  return jsonify({'response': 'حدث خطأ داخلي', 'error': str(e)}), 500
95
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  # ================= واجهة Gradio =================
 
97
  def launch_gradio():
98
  with gr.Blocks() as demo:
99
- gr.Markdown("# 🤖 مساعد نورا + نظام المهام الموزعة")
100
 
101
  with gr.Tab("المحادثة مع نورا"):
102
  chatbot = gr.Chatbot(type="messages")
@@ -104,13 +272,9 @@ def launch_gradio():
104
  send = gr.Button("إرسال")
105
 
106
  def respond(message, chat_history_display):
107
- # أرسل السؤال إلى الموديل
108
  answer = chat_with_nora(message)
109
-
110
- # أضف الرسالة وردها إلى واجهة Gradio
111
  chat_history_display.append({"role": "user", "content": message})
112
  chat_history_display.append({"role": "assistant", "content": answer})
113
-
114
  return "", chat_history_display
115
 
116
  send.click(respond, [msg, chatbot], [msg, chatbot])
@@ -122,9 +286,49 @@ def launch_gradio():
122
 
123
  run_button.click(run_task, inputs=task_dropdown, outputs=result_box)
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  demo.launch(server_name="0.0.0.0", server_port=7860)
126
 
 
127
  # ================= تشغيل الاثنين معًا =================
 
 
 
 
 
 
 
 
 
 
 
 
128
  if __name__ == '__main__':
 
 
 
 
129
  threading.Thread(target=launch_gradio, daemon=True).start()
130
- app.run(host='0.0.0.0', port=5000, debug=True)
 
 
 
1
  # -*- coding: utf-8 -*-
2
+ """
3
+ نصّ البرنامج: مساعد نورا + نظام مهام موزعة + خيار لتشغيل كل ملفات بايثون (.py) الموجودة في نفس المجلد.
4
+
5
+
6
+ الميزات المضافة/المعدّلة
7
+ ========================
8
+ 1. **تشغيل كل ملفات بايثون في نفس المجلد** يدويًا من واجهة Gradio أو تلقائيًا عند بدء التشغيل (يمكن التحكّم عبر متغيّر بيئة).
9
+ 2. جمع المخرجات (stdout / stderr) لكل ملف بشكل منفصل وإرجاعها/عرضها للمستخدم.
10
+ 3. استثناء الملف الحالي (الذي يحتوي الكود) تلقائيًا حتى لا يدخل في حلقة تشغيل ذاتي.
11
+ 4. استثناء مجلدات مثل `__pycache__` وأي ملف يبدأ بشرطة سفلية اختياريا.
12
+ 5. واجهة Gradio محدثة: تبويب جديد باسم "تشغيل جميع ملفات بايثون" يعرض قائمة الملفات + زر تشغيل + صندوق نتائج.
13
+ 6. دوال مساعدة نظيفة + تسجيل (logging) اختياري.
14
+
15
+
16
+ طريقة التحكم بالتشغيل التلقائي عند بدء البرنامج
17
+ ----------------------------------------------
18
+ - اضبط متغيّر البيئة: `RUN_ALL_PY_ON_START=1` لتشغيلها عند الإقلاع.
19
+ - أو اتركه فارغًا/0 لتعطيل التشغيل التلقائي.
20
+
21
+
22
+ ملاحظات مهمة
23
+ ------------
24
+ - التنفيذ يتم باستخدام `runpy.run_path()` بحيث يعمل كل ملف في مساحة أسماء مستقلة (قاموس).
25
+ - لا يتم عمل `import` مباشر؛ هذا يقلل تداخل الرموز ولكن *إذا كانت لديك ملفات تعتمد على الاستيراد النسبي* فربما تفضّل التحميل عبر `importlib`. (أنظر التعليق داخل الكود لاختيار وضع الاستيراد.)
26
+ - لو احتجت تمرير متغيرات/وسائط إلى هذه السكربتات الخارجية فسيتطلب ذلك بروتوكول إضافي (argparse مخفي، أو بيئة، أو ملف إعدادات مشترَك).
27
+
28
+
29
+ """
30
+
31
  import os
32
+ import io
33
+ import re
34
  import json
35
  import time
36
+ import glob
37
+ import runpy
38
+ import types
39
+ import logging
40
  import threading
41
+ from contextlib import redirect_stdout, redirect_stderr
42
+
43
  from flask import Flask, request, jsonify
44
  import gradio as gr
45
+
46
  from transformers import AutoTokenizer, AutoModelForCausalLM
47
  from accelerate import init_empty_weights, load_checkpoint_and_dispatch
48
+
49
+ # استيراد نظام التوزيع والمهام المعرّفة من ملفات أخرى
50
  from distributed_executor import DistributedExecutor
51
+ from your_tasks import * # يفترض أن يحتوي على matrix_multiply, prime_calculation, ... إلخ
52
 
53
  # ================= إعدادات الموديل =================
54
  local_model_path = "./Mistral-7B-Instruct-v0.1"
55
  offload_dir = "offload_dir"
56
  history_path = "history.json"
57
 
58
+ # ================= إعداد التسجيل (اختياري) =================
59
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
60
+ logger = logging.getLogger(__name__)
61
+
62
+ # ================= تحميل التوكنيزر والموديل =================
63
+ logger.info("Loading tokenizer: %s", local_model_path)
64
  tokenizer = AutoTokenizer.from_pretrained(local_model_path)
65
+
66
+ logger.info("Initializing empty weights model shell…")
67
  with init_empty_weights():
68
  model = AutoModelForCausalLM.from_pretrained(local_model_path)
69
+
70
+ logger.info("Dispatching model w/ CPU offload (accelerate)…")
71
  model = load_checkpoint_and_dispatch(
72
  model,
73
  local_model_path,
74
+ device_map={"": "cpu"}, # كلّه على CPU (يمكن التعديل لاحقًا)
75
  offload_folder=offload_dir,
76
+ offload_state_dict=True,
77
  )
78
 
79
+ # ================= تحميل سجل المحادثة =================
80
  if os.path.exists(history_path):
81
+ try:
82
+ with open(history_path, "r", encoding="utf-8") as f:
83
+ chat_history = json.load(f)
84
+ except Exception as e:
85
+ logger.warning("تعذّر تحميل سجل المحادثة (%s)، سيتم البدء بسجل فارغ.", e)
86
+ chat_history = []
87
  else:
88
  chat_history = []
89
 
90
+
91
  def format_chat(history):
92
  messages = [{"role": "system", "content": "أنت المساعدة نورا."}]
93
  messages.extend(history)
94
  return tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
95
 
96
+
97
  # ================= دالة الرد من نورا =================
98
  def chat_with_nora(user_input):
99
  global chat_history
 
102
  inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
103
  outputs = model.generate(**inputs, max_new_tokens=200, do_sample=True, temperature=0.7)
104
  response = tokenizer.decode(outputs[0], skip_special_tokens=True)
105
+ # محاولة استخراج آخر رد بعد قالب الدردشة
106
+ if "[/INST]" in response:
107
+ answer = response.split("[/INST]")[-1].strip()
108
+ else:
109
+ answer = response.strip()
110
  chat_history.append({"role": "assistant", "content": answer})
111
+ try:
112
+ with open(history_path, "w", encoding="utf-8") as f:
113
+ json.dump(chat_history, f, ensure_ascii=False, indent=2)
114
+ except Exception as e:
115
+ logger.error("Failed saving chat history: %s", e)
116
  return answer
117
 
118
+
119
  # ================= نظام توزيع المهام =================
120
  executor = DistributedExecutor("my_shared_secret_123")
121
  executor.peer_registry.register_service("node1", 7520, load=0.2)
122
 
123
+ # المهام المعرفة يدويًا
124
  tasks = {
125
  "ضرب المصفوفات": (matrix_multiply, 500),
126
  "حساب الأعداد الأولية": (prime_calculation, 100000),
127
  "معالجة البيانات": (data_processing, 10000),
128
  "محاكاة معالجة الصور": (image_processing_emulation, 100),
129
+ "مهمة موزعة معقدة": (lambda x: x * x, 42),
130
  }
131
 
132
+
133
  def run_task(task_name):
134
  if task_name in tasks:
135
  func, arg = tasks[task_name]
136
  start = time.time()
137
+ try:
138
+ result = func(arg)
139
+ status = "success"
140
+ except Exception as e:
141
+ result = f"ERROR: {e}"
142
+ status = "error"
143
  duration = time.time() - start
144
+ return f"✅ الحالة: {status}
145
+ النتيجة: {str(result)[:200]}...
146
+ ⏱ الوقت: {duration:.2f} ثانية"
147
  else:
148
  return "❌ مهمة غير موجودة!"
149
 
150
+
151
+ # =====================================================
152
+ # دعم تشغيل جميع ملفات بايثون في نفس المجلد
153
+ # =====================================================
154
+
155
+ def discover_py_files(directory=None, *, exclude_self=True, exclude_private=True):
156
+ """اكتشف جميع ملفات .py في المجلد (غير متداخلة) مع خيارات استثناء."""
157
+ directory = directory or os.path.dirname(os.path.abspath(__file__))
158
+ self_path = os.path.abspath(__file__)
159
+ files = []
160
+ for path in glob.glob(os.path.join(directory, "*.py")):
161
+ abs_path = os.path.abspath(path)
162
+ name = os.path.basename(abs_path)
163
+ if exclude_self and os.path.samefile(abs_path, self_path):
164
+ continue
165
+ if exclude_private and name.startswith("_"):
166
+ continue
167
+ files.append(abs_path)
168
+ return sorted(files)
169
+
170
+
171
+ def execute_single_py(path):
172
+ """شغّل ملف بايثون وأعد قاموسًا يحتوي stdout/stderr وزمن التنفيذ ونتيجة عودة محتملة."""
173
+ start = time.time()
174
+ stdout_buf = io.StringIO()
175
+ stderr_buf = io.StringIO()
176
+ result_namespace = None
177
+ error = None
178
+ try:
179
+ with redirect_stdout(stdout_buf), redirect_stderr(stderr_buf):
180
+ # run_path يعيد قاموس المتغيرات العالمية بعد التنفيذ
181
+ result_namespace = runpy.run_path(path_name=path, run_name="__main__")
182
+ except Exception as e: # noqa: BLE001 (نريد التقاط كل شيء هنا)
183
+ error = str(e)
184
+ duration = time.time() - start
185
+ return {
186
+ "file": path,
187
+ "stdout": stdout_buf.getvalue(),
188
+ "stderr": stderr_buf.getvalue(),
189
+ "error": error,
190
+ "globals": result_namespace if isinstance(result_namespace, dict) else {},
191
+ "time": duration,
192
+ }
193
+
194
+
195
+ def execute_all_py_files(directory=None, *, exclude_self=True, exclude_private=True):
196
+ files = discover_py_files(directory, exclude_self=exclude_self, exclude_private=exclude_private)
197
+ results = []
198
+ for fpath in files:
199
+ logger.info("Running: %s", fpath)
200
+ res = execute_single_py(fpath)
201
+ results.append(res)
202
+ return results
203
+
204
+
205
+ def summarize_execution_results(results):
206
+ """حوّل نتائج التنفيذ إلى نص قابل للعرض في Gradio."""
207
+ lines = []
208
+ for r in results:
209
+ lines.append("====================================")
210
+ lines.append(f"📄 الملف: {os.path.basename(r['file'])}")
211
+ lines.append(f"⏱ الوقت: {r['time']:.2f} ثانية")
212
+ if r["error"]:
213
+ lines.append(f"❌ خطأ: {r['error']}")
214
+ if r["stderr"].strip():
215
+ lines.append("--- STDERR ---")
216
+ lines.append(r["stderr"].strip())
217
+ if r["stdout"].strip():
218
+ lines.append("--- STDOUT ---")
219
+ # لا نعرض أكثر من 500 حرف لتجنب التضخم
220
+ out_preview = r["stdout"].strip()
221
+ if len(out_preview) > 500:
222
+ out_preview = out_preview[:500] + "... [تم الاقتصاص]"
223
+ lines.append(out_preview)
224
+ if not lines:
225
+ return "لا توجد ملفات للتشغيل."\
226
+
227
+ return "
228
+ ".join(lines)
229
+
230
+
231
  # ================= خادم Flask API =================
232
  app = Flask(__name__)
233
 
234
+
235
  @app.route('/api/chat', methods=['POST'])
236
  def api_chat():
237
  try:
 
244
  response_text = chat_with_nora(user_message)
245
  return jsonify({'response': response_text})
246
 
247
+ except Exception as e: # noqa: BLE001
248
+ logger.exception("/api/chat error")
249
  return jsonify({'response': 'حدث خطأ داخلي', 'error': str(e)}), 500
250
 
251
+
252
+ # ================ Flask: تشغيل كل الملفات عبر API (اختياري) ================
253
+ @app.route('/api/run_all', methods=['POST'])
254
+ def api_run_all():
255
+ try:
256
+ results = execute_all_py_files()
257
+ return jsonify({'results': results})
258
+ except Exception as e: # noqa: BLE001
259
+ logger.exception("/api/run_all error")
260
+ return jsonify({'error': str(e)}), 500
261
+
262
+
263
  # ================= واجهة Gradio =================
264
+
265
  def launch_gradio():
266
  with gr.Blocks() as demo:
267
+ gr.Markdown("# 🤖 مساعد نورا + نظام المهام الموزعة + تشغيل سكربتات المجلد")
268
 
269
  with gr.Tab("المحادثة مع نورا"):
270
  chatbot = gr.Chatbot(type="messages")
 
272
  send = gr.Button("إرسال")
273
 
274
  def respond(message, chat_history_display):
 
275
  answer = chat_with_nora(message)
 
 
276
  chat_history_display.append({"role": "user", "content": message})
277
  chat_history_display.append({"role": "assistant", "content": answer})
 
278
  return "", chat_history_display
279
 
280
  send.click(respond, [msg, chatbot], [msg, chatbot])
 
286
 
287
  run_button.click(run_task, inputs=task_dropdown, outputs=result_box)
288
 
289
+ with gr.Tab("تشغيل جميع ملفات بايثون"):
290
+ gr.Markdown("### اكتشاف وتشغيل جميع ملفات .py في نفس مجلد هذا البرنامج")
291
+ refresh_btn = gr.Button("🔄 تحديث قائمة الملفات")
292
+ run_all_btn = gr.Button("🚀 تشغيل الكل")
293
+ files_box = gr.Textbox(label="الملفات المكتشفة", interactive=False)
294
+ run_all_result = gr.Textbox(label="نتائج التشغيل", lines=15)
295
+
296
+ def refresh_files():
297
+ files = discover_py_files()
298
+ if not files:
299
+ return "لا توجد ملفات .py (باستثناء هذا الملف)."
300
+ return "
301
+ ".join(os.path.basename(f) for f in files)
302
+
303
+ def run_all_files_and_summarize():
304
+ results = execute_all_py_files()
305
+ return summarize_execution_results(results)
306
+
307
+ refresh_btn.click(refresh_files, outputs=files_box)
308
+ run_all_btn.click(run_all_files_and_summarize, outputs=run_all_result)
309
+
310
  demo.launch(server_name="0.0.0.0", server_port=7860)
311
 
312
+
313
  # ================= تشغيل الاثنين معًا =================
314
+ def startup_run_all_if_enabled():
315
+ run_flag = os.environ.get("RUN_ALL_PY_ON_START", "0").strip()
316
+ if run_flag in {"1", "true", "True", "yes", "YES"}:
317
+ logger.info("RUN_ALL_PY_ON_START=1 -> Running all py files at startup…")
318
+ results = execute_all_py_files()
319
+ summary = summarize_execution_results(results)
320
+ logger.info("Startup batch complete:
321
+ %s", summary)
322
+ else:
323
+ logger.info("Startup batch NOT enabled (set RUN_ALL_PY_ON_START=1 to enable).")
324
+
325
+
326
  if __name__ == '__main__':
327
+ # (اختياري) شغّل كل ملفات .py عند الإقلاع إن كان المستخدِم فعّل ذلك.
328
+ startup_run_all_if_enabled()
329
+
330
+ # شغّل Gradio في خيط مستقل
331
  threading.Thread(target=launch_gradio, daemon=True).start()
332
+
333
+ # شغّل Flask (المخدّم الأساسي)
334
+ app.run(host='0.0.0.0', port=5321, debug=True)