1Egyb commited on
Commit
35f3499
·
verified ·
1 Parent(s): 0c99645

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -82
app.py CHANGED
@@ -13,13 +13,9 @@ import PyPDF2
13
 
14
  # --------------------------- إعدادات عامة ---------------------------
15
  ALLOWED_EXTENSIONS = {'.py', '.js', '.html', '.css', '.txt', '.json', '.md', '.php', '.yml', '.yaml', '.docx', '.pdf'}
16
- MAX_FILE_SIZE_BYTES = 200 * 1024
17
- MAX_TOTAL_FILES = 500
18
- MAX_EXTRACTED_BYTES = 20 * 1024 * 1024
19
  MODEL_ID = os.environ.get('HF_MODEL_ID', 'moonshotai/Kimi-K2-Instruct')
20
  HF_TOKEN = os.environ.get('HF_TOKEN')
21
 
22
- # --------------------------- منطق Sandbox ---------------------------
23
  class Sandbox:
24
  def __init__(self):
25
  self.dir = None
@@ -27,46 +23,48 @@ class Sandbox:
27
 
28
  def cleanup(self):
29
  if self.dir and os.path.exists(self.dir):
30
- try:
31
- shutil.rmtree(self.dir)
32
  except: pass
33
  self.dir = None
34
  self.files_content = {}
35
 
36
- def reset(self, zip_file, password: Optional[str] = None):
37
- if zip_file is None:
38
- return "⚠️ يرجى رفع ملف ZIP أولاً"
39
 
40
  self.cleanup()
41
  self.dir = tempfile.mkdtemp(prefix="dev_sandbox_")
42
 
43
- try:
44
- # استخدام getattr للتعامل مع كائنات Gradio المختلفة
45
- zip_path = zip_file.name if hasattr(zip_file, 'name') else zip_file
46
-
47
- with pyzipper.AESZipFile(zip_path, 'r') as zf:
48
- if password:
49
- zf.setpassword(password.encode('utf-8'))
50
-
51
- for info in zf.infolist():
52
- if info.filename.startswith('__MACOSX') or info.is_dir():
53
- continue
54
- dest_path = os.path.normpath(os.path.join(self.dir, info.filename))
55
- if not dest_path.startswith(os.path.normpath(self.dir) + os.sep):
56
- continue
57
- zf.extract(info, path=self.dir)
58
-
59
- self.files_content = self.read_allowed_files(self.dir)
60
- return f"✅ تم تحميل {len(self.files_content)} ملف بنجاح"
61
- except Exception as e:
62
- return f"❌ خطأ أثناء الاستخراج: {str(e)}"
 
 
 
 
 
63
 
64
  def read_allowed_files(self, base_dir: str) -> Dict[str, str]:
65
  files = {}
66
- count = 0
67
  for path in Path(base_dir).rglob('*'):
68
  if path.is_file() and path.suffix.lower() in ALLOWED_EXTENSIONS:
69
- if count >= MAX_TOTAL_FILES: break
70
  rel = str(path.relative_to(base_dir))
71
  try:
72
  ext = path.suffix.lower()
@@ -79,9 +77,7 @@ class Sandbox:
79
  content = "\n".join([page.extract_text() or "" for page in reader.pages])
80
  else:
81
  content = path.read_text(encoding='utf-8', errors='ignore')
82
-
83
  files[rel] = content
84
- count += 1
85
  except: files[rel] = "/* تعذر قراءة الملف */"
86
  return files
87
 
@@ -102,92 +98,79 @@ class Sandbox:
102
  sandbox = Sandbox()
103
  client = InferenceClient(api_key=HF_TOKEN) if HF_TOKEN else None
104
 
105
- # --------------------------- دوال الدردشة والمعالجة ---------------------------
106
-
107
- def handle_user_msg(message, history):
108
- return "", history + [{"role": "user", "content": message}]
109
 
110
  def handle_assistant_res(history):
111
  if not HF_TOKEN or not client:
112
- history.append({"role": "assistant", "content": "⚠️ خطأ: HF_TOKEN غ��ر مضبوط."})
113
  yield history
114
  return
115
 
116
- context = "سياق المشروع:\n"
117
  for name, content in sandbox.files_content.items():
118
- context += f"\n--- FILE: {name} ---\n{content[:1500]}\n"
119
 
120
  system_instr = (
121
- "أنت مساعد برمجي خبير. عند طلب تعديل، أجب دائماً بالتنسيق التالي:\n"
122
- "[ملف: اسم_الملف_هنا]\n"
123
- "```language\nالمحتوى المحدث\n```"
 
 
 
124
  )
125
 
126
  user_query = history[-1]["content"]
127
- messages = [
128
- {"role": "system", "content": system_instr},
129
- {"role": "user", "content": f"{context}\n\nطلب المستخدم: {user_query}"}
130
- ]
131
 
132
  response = ""
133
  history.append({"role": "assistant", "content": ""})
134
 
135
  try:
136
- # تصحيح منطق استلام الـ Chunks لتجنب خطأ Index out of range
137
- for chunk in client.chat_completion(model=MODEL_ID, messages=messages, max_tokens=2048, stream=True):
138
- if hasattr(chunk, 'choices') and len(chunk.choices) > 0:
139
  token = chunk.choices[0].delta.content
140
  if token:
141
  response += token
142
  history[-1]["content"] = response
143
  yield history
144
  except Exception as e:
145
- history[-1]["content"] += f"\n\nخطأ أثناء البث: {str(e)}"
146
  yield history
147
 
148
- def run_apply_and_zip(history):
149
- if not history or history[-1]["role"] != "assistant":
150
- return None, "⚠️ لا يوجد رد من الوكيل لتطبيقه."
151
-
152
  text = history[-1]["content"]
 
153
  pattern = r"\[\s*(?:ملف|file)\s*:\s*(.*?)\s*\]\s*```.*?\n(.*?)```"
154
  matches = re.findall(pattern, text, re.DOTALL | re.IGNORECASE)
155
 
156
- if not matches:
157
- return None, "⚠️ لم أجد أكواداً بتنسيق [ملف: ...]"
158
 
159
  for name, code in matches:
160
  sandbox.update_file(name.strip(), code.strip())
161
 
162
  return sandbox.create_zip(), f"✅ تم تحديث {len(matches)} ملف."
163
 
164
- # --------------------------- واجهة Gradio ---------------------------
165
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
166
- gr.Markdown("# 🚀 Smart Dev Sandbox PRO")
167
 
168
  with gr.Row():
169
  with gr.Column(scale=2):
170
- chatbot = gr.Chatbot(type="messages", label="الدردشة البرمجية", height=450)
171
- msg_input = gr.Textbox(placeholder="اطلب تعديلاً على الملفات المرفوعة...", show_label=False)
172
- send_btn = gr.Button("إرسال الطلب ⚡", variant="primary")
173
 
174
  with gr.Column(scale=1):
175
- file_zip = gr.File(label="1. ارفع ملف ZIP")
176
- pwd = gr.Textbox(label="كلمة السر (اختياري)", type="password")
177
- load_btn = gr.Button("تحليل الملفات")
178
- status = gr.Markdown("🟢 **الحالة:** جاهز")
179
- apply_btn = gr.Button("2. تطبيق وحفظ ZIP", variant="stop")
180
- download = gr.File(label="تحميل المشروع")
181
-
182
- # الأحداث
183
- msg_input.submit(handle_user_msg, [msg_input, chatbot], [msg_input, chatbot]).then(
184
- handle_assistant_res, [chatbot], [chatbot]
185
- )
186
- send_btn.click(handle_user_msg, [msg_input, chatbot], [msg_input, chatbot]).then(
187
- handle_assistant_res, [chatbot], [chatbot]
188
- )
189
- load_btn.click(sandbox.reset, [file_zip, pwd], [status])
190
- apply_btn.click(run_apply_and_zip, [chatbot], [download, status])
191
-
192
- if __name__ == "__main__":
193
- demo.launch()
 
13
 
14
  # --------------------------- إعدادات عامة ---------------------------
15
  ALLOWED_EXTENSIONS = {'.py', '.js', '.html', '.css', '.txt', '.json', '.md', '.php', '.yml', '.yaml', '.docx', '.pdf'}
 
 
 
16
  MODEL_ID = os.environ.get('HF_MODEL_ID', 'moonshotai/Kimi-K2-Instruct')
17
  HF_TOKEN = os.environ.get('HF_TOKEN')
18
 
 
19
  class Sandbox:
20
  def __init__(self):
21
  self.dir = None
 
23
 
24
  def cleanup(self):
25
  if self.dir and os.path.exists(self.dir):
26
+ try: shutil.rmtree(self.dir)
 
27
  except: pass
28
  self.dir = None
29
  self.files_content = {}
30
 
31
+ def process_input_files(self, file_objects, password: Optional[str] = None):
32
+ if not file_objects:
33
+ return "⚠️ لم يتم اختيار أي ملفات."
34
 
35
  self.cleanup()
36
  self.dir = tempfile.mkdtemp(prefix="dev_sandbox_")
37
 
38
+ files_count = 0
39
+ # تحويل المدخل إلى قائمة دائماً
40
+ if not isinstance(file_objects, list):
41
+ file_objects = [file_objects]
42
+
43
+ for f_obj in file_objects:
44
+ path = f_obj.name
45
+ # إذا كان ملف ZIP
46
+ if path.lower().endswith('.zip'):
47
+ try:
48
+ with pyzipper.AESZipFile(path, 'r') as zf:
49
+ if password: zf.setpassword(password.encode('utf-8'))
50
+ for info in zf.infolist():
51
+ if info.filename.startswith('__MACOSX') or info.is_dir(): continue
52
+ zf.extract(info, path=self.dir)
53
+ files_count += 1
54
+ except Exception as e: return f"❌ خطأ في ZIP: {str(e)}"
55
+ else:
56
+ # ملفات عادية (DOCX, PDF, TXT...)
57
+ dest = os.path.join(self.dir, os.path.basename(path))
58
+ shutil.copy(path, dest)
59
+ files_count += 1
60
+
61
+ self.files_content = self.read_allowed_files(self.dir)
62
+ return f"✅ تم تحميل {len(self.files_content)} ملف (بما في ذلك محتويات ZIP)."
63
 
64
  def read_allowed_files(self, base_dir: str) -> Dict[str, str]:
65
  files = {}
 
66
  for path in Path(base_dir).rglob('*'):
67
  if path.is_file() and path.suffix.lower() in ALLOWED_EXTENSIONS:
 
68
  rel = str(path.relative_to(base_dir))
69
  try:
70
  ext = path.suffix.lower()
 
77
  content = "\n".join([page.extract_text() or "" for page in reader.pages])
78
  else:
79
  content = path.read_text(encoding='utf-8', errors='ignore')
 
80
  files[rel] = content
 
81
  except: files[rel] = "/* تعذر قراءة الملف */"
82
  return files
83
 
 
98
  sandbox = Sandbox()
99
  client = InferenceClient(api_key=HF_TOKEN) if HF_TOKEN else None
100
 
101
+ # --------------------------- منطق الدردشة ---------------------------
 
 
 
102
 
103
  def handle_assistant_res(history):
104
  if not HF_TOKEN or not client:
105
+ history.append({"role": "assistant", "content": "⚠️ توكن HF_TOKEN غير مضبوط."})
106
  yield history
107
  return
108
 
109
+ context = "الملفات المتاحة حالياً:\n"
110
  for name, content in sandbox.files_content.items():
111
+ context += f"\n[اسم الملف: {name}]\n{content[:1200]}\n"
112
 
113
  system_instr = (
114
+ "أنت مساعد برمجي. عندما يطلب المستخدم تعديل أو إنشاء ملف، يجب أن تلتزم بالتنسيق التالي حرفياً:\n"
115
+ "[ملف: اسم_الملف_كاملاً]\n"
116
+ "```\n"
117
+ "محتوى الملف هنا\n"
118
+ "```\n"
119
+ "لا تضف أي نص توضيحي داخل كتلة الكود، واجعل الرد يبدأ بـ [ملف: ...] لكل تعديل."
120
  )
121
 
122
  user_query = history[-1]["content"]
123
+ messages = [{"role": "system", "content": system_instr}, {"role": "user", "content": f"{context}\n\nالطلب: {user_query}"}]
 
 
 
124
 
125
  response = ""
126
  history.append({"role": "assistant", "content": ""})
127
 
128
  try:
129
+ for chunk in client.chat_completion(model=MODEL_ID, messages=messages, max_tokens=2500, stream=True):
130
+ if chunk.choices:
 
131
  token = chunk.choices[0].delta.content
132
  if token:
133
  response += token
134
  history[-1]["content"] = response
135
  yield history
136
  except Exception as e:
137
+ history[-1]["content"] += f"\n❌ خطأ: {str(e)}"
138
  yield history
139
 
140
+ def apply_changes(history):
141
+ if not history: return None, "الدردشة فارغة"
 
 
142
  text = history[-1]["content"]
143
+ # Regex محسن لالتقاط النمط المطلوب بدقة
144
  pattern = r"\[\s*(?:ملف|file)\s*:\s*(.*?)\s*\]\s*```.*?\n(.*?)```"
145
  matches = re.findall(pattern, text, re.DOTALL | re.IGNORECASE)
146
 
147
+ if not matches: return None, "⚠️ لم أجد ملفات للتعديل بتنسيق [ملف: ...]"
 
148
 
149
  for name, code in matches:
150
  sandbox.update_file(name.strip(), code.strip())
151
 
152
  return sandbox.create_zip(), f"✅ تم تحديث {len(matches)} ملف."
153
 
154
+ # --------------------------- الواجهة ---------------------------
155
+ with gr.Blocks(theme=gr.themes.Default()) as demo:
156
+ gr.Markdown("# 🚀 المحرر الذكي (دعم الملفات و ZIP)")
157
 
158
  with gr.Row():
159
  with gr.Column(scale=2):
160
+ chatbot = gr.Chatbot(type="messages", height=500)
161
+ msg_input = gr.Textbox(placeholder="مثال: أنشئ ملف README.md ترحيبي", show_label=False)
162
+ send_btn = gr.Button("إرسال", variant="primary")
163
 
164
  with gr.Column(scale=1):
165
+ file_input = gr.File(label="ارفع ZIP أو عدة ملفات", file_count="multiple")
166
+ pwd = gr.Textbox(label="كلمة السر (لـ ZIP)", type="password")
167
+ load_btn = gr.Button("تحليل الملفات المرفوعة")
168
+ status = gr.Markdown("🟢 جاهز")
169
+ apply_btn = gr.Button("حفظ وتحميل المشروع المعدل", variant="stop")
170
+ download = gr.File(label="المشروع النهائي")
171
+
172
+ load_btn.click(sandbox.process_input_files, [file_input, pwd], [status])
173
+ send_btn.click(lambda m, h: ("", h + [{"role": "user", "content": m}]), [msg_input, chatbot], [msg_input, chatbot]).then(handle_assistant_res, [chatbot], [chatbot])
174
+ apply_btn.click(apply_changes, [chatbot], [download, status])
175
+
176
+ demo.launch()