1Egyb commited on
Commit
642c5d6
·
verified ·
1 Parent(s): bb747c4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +230 -127
app.py CHANGED
@@ -2,173 +2,276 @@ import os
2
  import re
3
  import shutil
4
  import tempfile
 
5
  from pathlib import Path
6
- from typing import Dict, Tuple, Optional, List
7
 
8
- import pyzipper
9
  import gradio as gr
10
  from huggingface_hub import InferenceClient
11
- from docx import Document
12
- import PyPDF2
13
 
14
- # --------------------------- إعدادات عامة ---------------------------
15
- ALLOWED_EXTENSIONS = {'.py', '.js', '.html', '.css', '.txt', '.json', '.md', '.php', '.yml', '.yaml', '.docx', '.pdf', '.zip'}
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
22
- self.files_content = {}
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_files(self, file_objs, password: Optional[str] = None):
32
- if not file_objs:
33
- return "⚠️ لم يتم اختيار ملفات.", []
34
 
35
- self.cleanup()
36
- self.dir = tempfile.mkdtemp(prefix="dev_sandbox_")
 
 
 
 
 
37
 
38
- # التأكد من التعامل مع قائمة ملفات
39
- files = file_objs if isinstance(file_objs, list) else [file_objs]
 
40
 
41
- for f in files:
42
- original_path = f.name
43
- filename = os.path.basename(original_path)
44
-
45
  if filename.lower().endswith('.zip'):
46
  try:
47
- with pyzipper.AESZipFile(original_path, 'r') as zf:
48
- if password: zf.setpassword(password.encode('utf-8'))
49
- zf.extractall(path=self.dir)
50
- except Exception as e: return f"❌ خطأ في ZIP: {str(e)}", []
51
  else:
52
- dest = os.path.join(self.dir, filename)
53
- shutil.copy(original_path, dest)
54
-
55
- self.files_content = self.read_allowed_files(self.dir)
56
- # إرجاع الحالة وقائمة بأسماء الملفات للمستكشف
57
- file_list = list(self.files_content.keys())
58
- return f"✅ تم تحميل {len(file_list)} ملف بنجاح.", file_list
59
-
60
- def read_allowed_files(self, base_dir: str) -> Dict[str, str]:
61
- content_map = {}
62
- for path in Path(base_dir).rglob('*'):
63
  if path.is_file() and path.suffix.lower() in ALLOWED_EXTENSIONS:
64
- rel = str(path.relative_to(base_dir))
65
  try:
66
- ext = path.suffix.lower()
67
- if ext == '.docx':
68
- doc = Document(path)
69
- text = "\n".join([p.text for p in doc.paragraphs])
70
- elif ext == '.pdf':
71
- with open(path, 'rb') as pdf_f:
72
- reader = PyPDF2.PdfReader(pdf_f)
73
- text = "\n".join([page.extract_text() or "" for page in reader.pages])
74
- else:
75
- text = path.read_text(encoding='utf-8', errors='ignore')
76
- content_map[rel] = text
77
- except: content_map[rel] = "/* خطأ في القراءة */"
78
- return content_map
79
-
80
- def update_file(self, name: str, content: str):
81
- if not self.dir: return
82
- full_path = os.path.join(self.dir, name)
83
  os.makedirs(os.path.dirname(full_path), exist_ok=True)
84
  with open(full_path, 'w', encoding='utf-8') as f:
85
  f.write(content)
86
- self.files_content[name] = content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- def create_zip(self):
89
- if not self.dir: return None
90
- out_base = tempfile.mktemp()
91
- shutil.make_archive(out_base, 'zip', self.dir)
92
- return out_base + ".zip"
93
 
94
- sandbox = Sandbox()
 
 
 
 
 
 
 
 
 
95
  client = InferenceClient(api_key=HF_TOKEN) if HF_TOKEN else None
96
 
97
- # --------------------------- منطق المعالجة ---------------------------
98
 
99
- def handle_ai_chat(history):
100
  if not client:
101
- history.append({"role": "assistant", "content": "⚠️ HF_TOKEN غير متوفر."})
102
- yield history
103
- return
104
 
105
- context = "الملفات المرفوعة:\n"
106
- for name, content in sandbox.files_content.items():
107
- context += f"\n[ملف: {name}]\n{content[:1000]}\n"
 
108
 
109
  system_prompt = (
110
- "أنت مساعد برمجي. لإجراء تعديلات، استخدم التنسيق التالي حصراً:\n"
111
- "[ملف: اسم_الملف]\n"
112
- "```\nمحتوى الملف المحدث بالكامل\n```"
 
 
 
 
113
  )
114
-
115
- messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": f"{context}\n\nطلب المستخدم: {history[-1]['content']}"}]
116
-
117
- full_res = ""
118
- history.append({"role": "assistant", "content": ""})
 
 
119
  try:
120
- for chunk in client.chat_completion(model=MODEL_ID, messages=messages, max_tokens=2500, stream=True):
121
- if chunk.choices:
122
- token = chunk.choices[0].delta.content
123
- if token:
124
- full_res += token
125
- history[-1]["content"] = full_res
126
- yield history
127
  except Exception as e:
128
- history[-1]["content"] = f"❌ خطأ اتصال: {str(e)}"
129
- yield history
130
 
131
- def execute_changes(history):
132
- if not history: return None, "الدردشة فارغة."
133
- text = history[-1]["content"]
134
- matches = re.findall(r"\[\s*(?:ملف|file)\s*:\s*(.*?)\s*\]\s*```.*?\n(.*?)```", text, re.DOTALL | re.IGNORECASE)
135
 
136
- if not matches: return None, "لم يتم العثور على تعديلات بصيغة [ملف: ...]"
 
 
 
 
137
 
138
- for name, code in matches:
139
- sandbox.update_file(name.strip(), code.strip())
140
 
141
- return sandbox.create_zip(), f"✅ تم تحديث {len(matches)} ملفات."
 
 
142
 
143
- # --------------------------- الواجهة ---------------------------
144
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
145
- gr.Markdown("# 🚀 Sandbox المحرر الذكي \n ارفع ملفاتك، اطلب التعديل، وحمل النتيجة.")
146
 
 
 
 
147
  with gr.Row():
148
- with gr.Column(scale=2):
149
- chatbot = gr.Chatbot(type="messages", height=500)
150
- msg_in = gr.Textbox(placeholder="اكتب طلبك هنا (مثلاً: عدل README.md ليصبح بالعربية)...", show_label=False)
151
- send_btn = gr.Button("إرسال", variant="primary")
152
-
153
- with gr.Column(scale=1):
154
- # استخدام UploadButton بدلاً من File لضمان التوافق مع الجوال
155
- upload_btn = gr.UploadButton("📁 اختر ملفات أو ZIP", file_types=list(ALLOWED_EXTENSIONS), file_count="multiple")
156
- pwd_in = gr.Textbox(label="كلمة سر ZIP", type="password")
157
- status_md = gr.Markdown("🟢 بانتظار الملفات...")
158
 
159
- with gr.Accordion("📄 مستكشف الملفات المرفوعة", open=False):
160
- file_explorer = gr.JSON(value=[], label="الملفات النشطة في Sandbox")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
- apply_btn = gr.Button("تنفيذ التعديلات وحفظ", variant="stop")
163
- download_file = gr.File(label="تحميل المشروع النهائي")
164
 
165
- # ربط العمليات
166
- upload_btn.upload(sandbox.process_files, [upload_btn, pwd_in], [status_md, file_explorer])
 
 
 
 
 
167
 
168
- send_btn.click(lambda m, h: ("", h + [{"role": "user", "content": m}]), [msg_in, chatbot], [msg_in, chatbot]).then(
169
- handle_ai_chat, [chatbot], [chatbot]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  )
171
-
172
- apply_btn.click(execute_changes, [chatbot], [download_file, status_md])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
- demo.launch()
 
 
 
2
  import re
3
  import shutil
4
  import tempfile
5
+ import zipfile
6
  from pathlib import Path
7
+ from typing import Dict, List, Optional
8
 
 
9
  import gradio as gr
10
  from huggingface_hub import InferenceClient
 
 
11
 
12
+ # --------------------------- إعدادات النظام ---------------------------
13
+ # ندعم ملفات الكود النصية والصور الأساسية (للمعاينة المستقبلية)
14
+ ALLOWED_EXTENSIONS = {'.py', '.js', '.html', '.css', '.txt', '.json', '.md', '.php', '.xml', '.csv'}
15
+ MODEL_ID = os.environ.get('HF_MODEL_ID', 'moonshotai/Kimi-K2-Instruct') # نموذج قوي بذاكرة كبيرة
16
+ HF_TOKEN = os.environ.get('HF_TOKEN')
17
 
18
+ class ProjectManager:
19
  def __init__(self):
20
+ self.work_dir = tempfile.mkdtemp(prefix="ai_ide_")
21
+ self.files: Dict[str, str] = {} # تخزين المسار النسبي والمحتوى
22
+ self.active_file = None
23
+
24
+ def load_files(self, file_objs):
25
+ """تحميل الملفات أو فك ضغط ZIP مع الحفاظ على الهيكلية"""
26
+ if not file_objs: return "⚠️ لم يتم اختيار ملفات.", []
 
 
 
 
 
 
27
 
28
+ # تنظيف القديم
29
+ self.files = {}
30
+ if os.path.exists(self.work_dir):
31
+ shutil.rmtree(self.work_dir)
32
+ self.work_dir = tempfile.mkdtemp(prefix="ai_ide_")
33
+
34
+ file_list_objs = file_objs if isinstance(file_objs, list) else [file_objs]
35
 
36
+ for f_obj in file_list_objs:
37
+ file_path = f_obj.name
38
+ filename = os.path.basename(file_path)
39
 
 
 
 
 
40
  if filename.lower().endswith('.zip'):
41
  try:
42
+ with zipfile.ZipFile(file_path, 'r') as zf:
43
+ zf.extractall(self.work_dir)
44
+ except Exception as e:
45
+ return f"❌ خطأ في فك الضغط: {str(e)}", []
46
  else:
47
+ shutil.copy(file_path, os.path.join(self.work_dir, filename))
48
+
49
+ # قراءة الملفات من القرص إلى الذاكرة
50
+ self.refresh_file_memory()
51
+ return f"✅ تم تحميل المشروع ({len(self.files)} ملف).", list(self.files.keys())
52
+
53
+ def refresh_file_memory(self):
54
+ """قراءة الملفات فعلياً من المجلد المؤقت"""
55
+ self.files = {}
56
+ base_path = Path(self.work_dir)
57
+ for path in base_path.rglob('*'):
58
  if path.is_file() and path.suffix.lower() in ALLOWED_EXTENSIONS:
59
+ rel_path = str(path.relative_to(base_path)).replace("\\", "/")
60
  try:
61
+ content = path.read_text(encoding='utf-8', errors='ignore')
62
+ self.files[rel_path] = content
63
+ except:
64
+ pass
65
+
66
+ def get_file_content(self, filename):
67
+ return self.files.get(filename, "")
68
+
69
+ def update_file(self, filename, content):
70
+ """تحديث ملف (سواء من المحرر اليدوي أو الذكاء الاصطناعي)"""
71
+ full_path = os.path.join(self.work_dir, filename)
 
 
 
 
 
 
72
  os.makedirs(os.path.dirname(full_path), exist_ok=True)
73
  with open(full_path, 'w', encoding='utf-8') as f:
74
  f.write(content)
75
+ self.files[filename] = content
76
+ return f"تم تحديث {filename}"
77
+
78
+ def generate_preview(self):
79
+ """
80
+ 🔥 المحرك السحري للمعاينة 🔥
81
+ يقوم بدمج ملفات CSS و JS داخل ملف HTML ليعمل كملف واحد.
82
+ """
83
+ index_file = None
84
+ # البحث عن ملف البداية
85
+ candidates = ["index.html", "main.html", "home.html"]
86
+ for c in candidates:
87
+ if c in self.files:
88
+ index_file = c
89
+ break
90
+
91
+ if not index_file:
92
+ # البحث عن أي ملف html
93
+ for f in self.files:
94
+ if f.endswith(".html"):
95
+ index_file = f
96
+ break
97
+
98
+ if not index_file:
99
+ return "<div style='padding:20px; text-align:center'>⚠️ لم يتم العثور على ملف HTML للمعاينة.</div>"
100
+
101
+ html_content = self.files[index_file]
102
+
103
+ # 1. حقن CSS (بحث عن <link rel="stylesheet">)
104
+ def replace_css(match):
105
+ href = match.group(1)
106
+ # محاولة إيجاد الملف بناء على المسار النسبي
107
+ if href in self.files:
108
+ return f"<style>\n/* Injected: {href} */\n{self.files[href]}\n</style>"
109
+ return match.group(0) # إبقاءه كما هو إذا لم يوجد الملف
110
+
111
+ html_content = re.sub(r'<link[^>]+href=["\'](.*?)["\'][^>]*>', replace_css, html_content)
112
+
113
+ # 2. حقن JS (بحث عن <script src="...">)
114
+ def replace_js(match):
115
+ src = match.group(1)
116
+ if src in self.files:
117
+ return f"<script>\n/* Injected: {src} */\n{self.files[src]}\n</script>"
118
+ return match.group(0)
119
 
120
+ html_content = re.sub(r'<script[^>]+src=["\'](.*?)["\'][^>]*>\s*</script>', replace_js, html_content)
 
 
 
 
121
 
122
+ return html_content
123
+
124
+ def export_zip(self):
125
+ if not self.work_dir: return None
126
+ out_file = tempfile.mktemp()
127
+ shutil.make_archive(out_file, 'zip', self.work_dir)
128
+ return out_file + ".zip"
129
+
130
+ # تهيئة المدير والعميل
131
+ pm = ProjectManager()
132
  client = InferenceClient(api_key=HF_TOKEN) if HF_TOKEN else None
133
 
134
+ # --------------------------- منطق الذكاء الاصطناعي ---------------------------
135
 
136
+ def ai_chat_handler(message, history):
137
  if not client:
138
+ return "⚠️ يجب وضع HF_TOKEN في متغيرات البيئة."
 
 
139
 
140
+ # تجميع السياق الكامل (بلا حدود أحرف - الدبابة!)
141
+ context_str = "الملفات الحالية في المشروع:\n"
142
+ for name, content in pm.files.items():
143
+ context_str += f"\n--- FILE: {name} ---\n{content}\n"
144
 
145
  system_prompt = (
146
+ "أنت بيئة تطوير متكاملة (AI IDE). "
147
+ همتك: تعديل الكود وتطويره بناء على الطلب. "
148
+ "القواعد الصارمة:\n"
149
+ "1. اقرأ كل الملفات بعناية.\n"
150
+ "2. عند تعديل ملف، يجب كتابة الكود كاملاً داخل البلوك.\n"
151
+ "3. استخدم الصيغة التالية للإخراج:\n"
152
+ "[TARGET: filename.ext]\n```\n... code ...\n```"
153
  )
154
+
155
+ messages = [
156
+ {"role": "system", "content": system_prompt},
157
+ {"role": "user", "content": context_str + f"\n\nالمطلوب: {message}"}
158
+ ]
159
+
160
+ response_text = ""
161
  try:
162
+ # استخدام stream للحصول على استجابة سريعة
163
+ stream = client.chat_completion(model=MODEL_ID, messages=messages, max_tokens=4000, stream=True)
164
+ for chunk in stream:
165
+ if chunk.choices and chunk.choices[0].delta.content:
166
+ response_text += chunk.choices[0].delta.content
167
+ yield response_text
 
168
  except Exception as e:
169
+ yield f"❌ خطأ: {str(e)}"
 
170
 
171
+ def apply_ai_changes(response_text):
172
+ """تحليل رد الذكاء الاصطناعي وتنفيذ التغييرات"""
173
+ pattern = r"\[TARGET:\s*(.*?)\]\s*```.*?\n(.*?)```"
174
+ matches = re.findall(pattern, response_text, re.DOTALL | re.IGNORECASE)
175
 
176
+ log = []
177
+ for filename, code in matches:
178
+ filename = filename.strip()
179
+ pm.update_file(filename, code.strip())
180
+ log.append(f"✅ تم تحديث {filename}")
181
 
182
+ if not log:
183
+ return "⚠️ لم يتم العثور على كود للتنفيذ. تأكد أن الرد يحتوي على [TARGET: file]...", pm.generate_preview()
184
 
185
+ return "\n".join(log), pm.generate_preview()
186
+
187
+ # --------------------------- واجهة المستخدم (Gradio Blocks) ---------------------------
188
 
189
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue", secondary_hue="slate"), title="AI IDE Pro") as demo:
 
 
190
 
191
+ # حالة التطبيق (State)
192
+ selected_file_state = gr.State()
193
+
194
  with gr.Row():
195
+ gr.Markdown("## 🛠️ AI IDE Pro: بيئة تطوير كاملة")
196
+
197
+ with gr.Row(equal_height=True):
198
+ # العمود الأيسر: الأدوات والملفات
199
+ with gr.Column(scale=1, variant="panel"):
200
+ upload_btn = gr.UploadButton("📂 فتح مشروع (ZIP/Files)", file_count="multiple", file_types=list(ALLOWED_EXTENSIONS))
201
+ file_list = gr.Dropdown(label="ملفات المشروع", interactive=True)
202
+ status_bar = gr.Markdown(" جاهز...")
 
 
203
 
204
+ gr.Markdown("### 🤖 المبرمج الذكي")
205
+ chatbot = gr.Chatbot(height=300, type="messages")
206
+ msg_input = gr.Textbox(placeholder="اطلب تعديلاً (مثلاً: غير لون الخلفية للأحمر)...", lines=2)
207
+ send_btn = gr.Button("إرسال الطلب", variant="primary")
208
+ apply_btn = gr.Button("⚡ تنفيذ التعديلات", variant="stop")
209
+
210
+ # العمود الأيمن: المحرر والمعاينة
211
+ with gr.Column(scale=2):
212
+ with gr.Tabs():
213
+ with gr.TabItem("📝 المحرر (Code Editor)"):
214
+ code_editor = gr.Code(label="Source Code", language="html", interactive=True, lines=20)
215
+ save_manual_btn = gr.Button("حفظ التعديلات اليدوية", size="sm")
216
+
217
+ with gr.TabItem("🌐 معاينة حية (Live Preview)"):
218
+ preview_html = gr.HTML(label="Web Preview")
219
+ refresh_preview_btn = gr.Button("🔄 تحديث المعاينة")
220
 
221
+ download_btn = gr.File(label=صدير المشروع النهائي")
 
222
 
223
+ # ------------------ ربط الأحداث (Event Wiring) ------------------
224
+
225
+ # 1. رفع الملفات
226
+ def on_upload(files):
227
+ msg, list_files = pm.load_files(files)
228
+ preview = pm.generate_preview()
229
+ return msg, gr.update(choices=list_files, value=list_files[0] if list_files else None), preview
230
 
231
+ upload_btn.upload(on_upload, upload_btn, [status_bar, file_list, preview_html])
232
+
233
+ # 2. اختيار ملف من القائمة لعرضه في المحرر
234
+ def on_file_select(filename):
235
+ content = pm.get_file_content(filename)
236
+ lang = "html"
237
+ if filename.endswith(".js"): lang = "javascript"
238
+ elif filename.endswith(".py"): lang = "python"
239
+ elif filename.endswith(".css"): lang = "css"
240
+ return content, lang, filename
241
+
242
+ file_list.change(on_file_select, file_list, [code_editor, code_editor, selected_file_state])
243
+
244
+ # 3. حفظ يدوي من المحرر
245
+ def on_manual_save(code, filename):
246
+ if not filename: return "⚠️ لم يتم اختيار ملف.", pm.generate_preview()
247
+ msg = pm.update_file(filename, code)
248
+ return msg, pm.generate_preview()
249
+
250
+ save_manual_btn.click(on_manual_save, [code_editor, selected_file_state], [status_bar, preview_html])
251
+
252
+ # 4. الشات والذكاء الاصطناعي
253
+ msg_input.submit(lambda m, h: ("", h + [{"role": "user", "content": m}]), [msg_input, chatbot], [msg_input, chatbot]).then(
254
+ ai_chat_handler, [msg_input, chatbot], chatbot
255
  )
256
+ send_btn.click(lambda m, h: ("", h + [{"role": "user", "content": m}]), [msg_input, chatbot], [msg_input, chatbot]).then(
257
+ ai_chat_handler, [msg_input, chatbot], chatbot
258
+ )
259
+
260
+ # 5. تنفيذ تعديلات الذكاء الاصطناعي
261
+ def execute_ai(history):
262
+ if not history: return "لا يوجد محادثة", None, None
263
+ last_response = history[-1]['content']
264
+ log, preview = apply_ai_changes(last_response)
265
+ # تحديث قائمة الملفات لأن الـ AI قد ينشئ ملفات جديدة
266
+ new_file_list = list(pm.files.keys())
267
+ zip_path = pm.export_zip()
268
+ return log, preview, gr.update(choices=new_file_list), zip_path
269
+
270
+ apply_btn.click(execute_ai, [chatbot], [status_bar, preview_html, file_list, download_btn])
271
+
272
+ # 6. تحديث المعاينة يدوياً
273
+ refresh_preview_btn.click(lambda: pm.generate_preview(), None, preview_html)
274
 
275
+ # تشغيل التطبيق
276
+ if __name__ == "__main__":
277
+ demo.queue().launch()