1Egyb commited on
Commit
120041a
·
verified ·
1 Parent(s): 7857882

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -204
app.py CHANGED
@@ -7,257 +7,171 @@ from pathlib import Path
7
  import gradio as gr
8
  from huggingface_hub import InferenceClient
9
 
10
- # --------------------------- التكوين الأساسي (The Core) ---------------------------
11
- # نستخدم Qwen لأنه الأقوى حالياً في الكود والمنطق المعقد
12
- MODEL_ID = os.environ.get('HF_MODEL_ID', 'Qwen/Qwen2.5-Coder-32B-Instruct')
 
 
 
 
13
  HF_TOKEN = os.environ.get('HF_TOKEN')
14
 
15
- class PowerTankIDE:
16
  def __init__(self):
17
- # إنشاء بيئة عمل معزولة (Sandbox)
18
- self.work_dir = tempfile.mkdtemp(prefix="tank_power_")
19
- self.files_memory = {} # الذاكرة الحية: {filename: content}
20
- self.project_tree = "" # الخريطة الذهنية للمشروع
21
 
22
- def load_project(self, files):
23
- """تحميل المشروع وتفكيكه بذكاء"""
24
- if not files: return "⚠️ لا توجد ملفات.", []
25
 
26
- # تنظيف الساحة لبدء معركة جديدة
27
  if os.path.exists(self.work_dir):
28
  shutil.rmtree(self.work_dir)
29
- self.work_dir = tempfile.mkdtemp(prefix="tank_power_")
30
-
31
- file_list = files if isinstance(files, list) else [files]
32
- for f in file_list:
33
- if f.name.lower().endswith('.zip'):
 
34
  with zipfile.ZipFile(f.name, 'r') as z:
35
  z.extractall(self.work_dir)
36
  else:
37
- dest = os.path.join(self.work_dir, os.path.basename(f.name))
38
  shutil.copy(f.name, dest)
39
 
40
- self.sync_system()
41
- return f" تم تحميل النظام: {len(self.files_memory)} ملفات جاهزة.", list(self.files_memory.keys())
42
-
43
- def sync_system(self):
44
- """مزامنة الواقع (القرص) مع الوعي (الذاكرة)"""
45
- self.files_memory = {}
46
- tree_lines = []
47
-
48
- # قراءة الملفات وبناء الهيكل
49
- for path in Path(self.work_dir).rglob('*'):
50
- if path.is_file() and not path.name.startswith('.'): # تجاهل ملفات النظام المخفية
51
- rel_path = str(path.relative_to(self.work_dir)).replace("\\", "/")
52
-
53
- # قراءة الملفات النصية فقط للذاكرة
54
- if path.suffix in ['.html', '.css', '.js', '.py', '.json', '.md', '.txt', '.xml']:
55
- try:
56
- content = path.read_text(encoding='utf-8', errors='ignore')
57
- self.files_memory[rel_path] = content
58
- tree_lines.append(f"- {rel_path}")
59
- except:
60
- tree_lines.append(f"- {rel_path} (Binary/Unreadable)")
61
- else:
62
- tree_lines.append(f"- {rel_path} (Asset)")
63
-
64
- self.project_tree = "\n".join(tree_lines)
65
-
66
- def get_context(self, query):
67
- """استدعاء الملفات ذات الصلة فقط (توفير الذاكرة للقوة العقلية)"""
68
- context_str = f"### Project Structure ###\n{self.project_tree}\n"
69
-
70
- # خوارزمية بسيطة لتحديد الملفات المهمة بناءً على الطلب
71
- # 1. دائماً نرفق ملفات الهيكل الأساسي
72
- priority_files = ['index.html', 'styles.css', 'style.css', 'main.js', 'app.js']
73
-
74
- # 2. نرفق الملفات التي ذكرها المستخدم صراحة
75
- query_lower = query.lower()
76
-
77
- attached_count = 0
78
- for name, content in self.files_memory.items():
79
- is_priority = any(p == name for p in priority_files)
80
- is_requested = name.lower() in query_lower
81
-
82
- # نرفق الملف إذا كان أولوية أو تم طلبه، بشرط ألا نغرق الذاكرة
83
- if (is_priority or is_requested) and attached_count < 5:
84
- context_str += f"\n--- FILE: {name} ---\n{content}\n"
85
- attached_count += 1
86
-
87
- return context_str
88
-
89
- def force_update(self, filename, content):
90
- """تحديث الملف بقوة على القرص والذاكرة"""
91
- full_path = os.path.join(self.work_dir, filename)
92
- os.makedirs(os.path.dirname(full_path), exist_ok=True)
93
- with open(full_path, 'w', encoding='utf-8') as f:
94
  f.write(content)
95
- self.files_memory[filename] = content
96
-
97
- def compile_preview(self):
98
- """المجمع (Compiler): يجمع الشتات ليصنع واجهة تعمل"""
99
- # البحث عن نقطة الدخول
100
- index = next((f for f in ["index.html", "main.html"] if f in self.files_memory),
101
- next((f for f in self.files_memory if f.endswith('.html')), None))
 
102
 
103
- if not index:
104
- return "<div style='display:flex;justify-content:center;align-items:center;height:100vh;color:#e74c3c;font-family:sans-serif;'><h2>⛔ خطأ: لا يوجد ملف HTML رئيسي (index.html)</h2></div>"
105
 
106
- html_content = self.files_memory[index]
107
-
108
- # حقن الـ CSS قسرياً في الرأس
109
- css_blob = "\n".join([f"<style>/* {n} */\n{c}\n</style>" for n, c in self.files_memory.items() if n.endswith('.css')])
110
- if "</head>" in html_content:
111
- html_content = html_content.replace("</head>", f"{css_blob}\n</head>")
112
- else:
113
- html_content = f"<head>{css_blob}</head>" + html_content
114
 
115
- # حقن الـ JS قسرياً في النهاية
116
- js_blob = "\n".join([f"<script>/* {n} */\n{c}\n</script>" for n, c in self.files_memory.items() if n.endswith('.js')])
117
- if "</body>" in html_content:
118
- html_content = html_content.replace("</body>", f"{js_blob}\n</body>")
119
- else:
120
- html_content += js_blob
121
 
122
- return html_content
 
 
 
 
123
 
124
- def export_zip(self):
125
- out = tempfile.mktemp()
126
- shutil.make_archive(out, 'zip', self.work_dir)
127
- return out + ".zip"
128
 
129
- # تهيئة المحرك
130
- tank = PowerTankIDE()
131
  client = InferenceClient(api_key=HF_TOKEN) if HF_TOKEN else None
132
 
133
- # --------------------------- العقل المدبر (AI Logic) ---------------------------
134
 
135
- def engine_response(message, history):
 
 
 
 
136
  if not client:
137
- yield history + [{"role": "assistant", "content": "⚠️ النظام متوقف: HF_TOKEN غير موجود."}]
138
  return
139
 
140
- context = tank.get_context(message)
141
- system_prompt = (
142
- "أنت 'Tank Engine'، بيئة تطوير قوية (IDE). هدفك: كتابة كود يعمل (Functional Code).\n"
143
- "المخرجات: [FILE: filename] يليها ```code```. أعد كتابة الملف كاملاً."
 
 
 
 
144
  )
145
 
146
- messages = [{"role": "system", "content": system_prompt}]
147
- for msg in history:
148
- if isinstance(msg, dict): messages.append(msg)
149
- messages.append({"role": "user", "content": f"{context}\n\nTask: {message}"})
150
 
151
- full_response = ""
152
  history.append({"role": "user", "content": message})
153
  history.append({"role": "assistant", "content": ""})
154
 
155
- try:
156
- stream = client.chat_completion(model=MODEL_ID, messages=messages, max_tokens=8000, stream=True)
157
- for chunk in stream:
158
- # التحصين هنا: نتحقق من وجود 'choices' ومن أنها ليست فارغة
159
- if hasattr(chunk, 'choices') and chunk.choices and len(chunk.choices) > 0:
160
- token = chunk.choices[0].delta.content
161
- if token:
162
- full_response += token
163
- history[-1]["content"] = full_response
164
- yield history
165
- else:
166
- # إذا وصلنا هنا، يعني أن هناك Chunk تقني لا يحتوي على نص
167
- continue
168
- except Exception as e:
169
- history[-1]["content"] = f"❌ انقطع الاتصال: {str(e)}"
170
- yield history
171
-
172
- def apply_updates(history):
173
- """تنفيذ التعديلات واستخراج الملفات"""
174
- if not history: return "لا يوجد بيانات", None, None
175
 
176
- raw_text = history[-1]["content"]
177
- # نمط استخراج قوي
178
- pattern = r"\[FILE:\s*(.*?)\]\s*```.*?\n(.*?)```"
179
- matches = re.findall(pattern, raw_text, re.DOTALL | re.IGNORECASE)
180
 
181
- logs = []
182
- if not matches:
183
- return "⚠️ لم يتم العثور على كود قابل للتنفيذ.", tank.compile_preview(), list(tank.files_memory.keys())
184
-
185
  for filename, code in matches:
186
- clean_name = filename.strip()
187
- tank.force_update(clean_name, code.strip())
188
- logs.append(f"✅ تم تحديث: {clean_name}")
189
 
190
- tank.sync_system() # تحديث الخريطة
191
- return "\n".join(logs), tank.compile_preview(), list(tank.files_memory.keys())
192
 
193
- # --------------------------- واجهة التحكم (Cockpit) ---------------------------
194
 
195
- with gr.Blocks(theme=gr.themes.Monochrome(), title="Tank IDE - Power Edition") as demo:
196
- gr.Markdown("# 🛡️ Tank IDE <span style='font-size:0.6em; opacity:0.5'>Power Edition</span>")
197
-
198
- active_file = gr.State()
199
 
 
 
 
 
200
  with gr.Row():
201
- # العمود الأيسر: التحكم والملفات
202
- with gr.Column(scale=1, variant="panel"):
203
- with gr.Group():
204
- upload = gr.UploadButton("📂 تحميل المشروع (ZIP)", file_count="multiple", variant="secondary")
205
- file_dropdown = gr.Dropdown(label="ملفات المشروع", interactive=True)
206
- download = gr.DownloadButton("💾 تصدير المشروع (ZIP)")
207
-
208
- status = gr.Markdown("جاهز للعمل.")
209
 
210
- # الشات - العقل المدبر
211
- chatbot = gr.Chatbot(height=500, type="messages", show_label=False)
212
- msg = gr.Textbox(placeholder="اكتب الأمر البرمجي هنا (مثلاً: أضف مكتبة Charts واعرض بيانات المبيعات)", show_label=False)
213
-
214
- with gr.Row():
215
- btn_send = gr.Button("تنفيذ الأمر", variant="primary")
216
- btn_apply = gr.Button("تطبيق التغييرات ⚡", variant="stop")
217
 
218
- # العمود الأيمن: المعاينة والكود
219
  with gr.Column(scale=2):
220
  with gr.Tabs():
221
- with gr.TabItem("🌐 المعاينة الحية"):
222
- preview = gr.HTML(label="المخرج النهائي", min_height=600)
223
-
224
- with gr.TabItem("💻 الكود المصدري"):
225
- editor = gr.Code(label="محرر الملفات", language="python", lines=30, interactive=True)
226
- btn_save_manual = gr.Button("حفظ تعديل يدوي", size="sm")
227
-
228
- # --------------------------- التشبيك (Wiring) ---------------------------
229
-
230
- # 1. التحميل
231
- def on_load(f):
232
- log, files = tank.load_project(f)
233
- return log, gr.update(choices=files), tank.compile_preview()
234
 
235
- upload.upload(on_load, upload, [status, file_dropdown, preview])
236
-
237
- # 2. الشات والتنفيذ
238
- btn_send.click(engine_response, [msg, chatbot], chatbot).then(lambda: "", None, msg)
239
 
240
- def on_apply_click(h):
241
- log, html, files = apply_updates(h)
242
- return log, html, gr.update(choices=files)
243
 
244
- btn_apply.click(on_apply_click, chatbot, [status, preview, file_dropdown])
245
-
246
- # 3. عرض الملفات وتعديلها يدوياً
247
- def on_select_file(f):
248
- return tank.files_memory.get(f, ""), f
249
-
250
- file_dropdown.change(on_select_file, file_dropdown, [editor, active_file])
251
-
252
- def on_manual_save(code, name):
253
- if not name: return "⚠️ اختر ملفاً أولاً"
254
- tank.force_update(name, code)
255
- return f"✅ تم حفظ {name} يدوياً"
256
-
257
- btn_save_manual.click(on_manual_save, [editor, active_file], status).then(tank.compile_preview, None, preview)
258
 
259
- # 4. التصدير
260
- download.click(tank.export_zip, None, download)
261
 
262
  if __name__ == "__main__":
263
  demo.queue().launch()
 
7
  import gradio as gr
8
  from huggingface_hub import InferenceClient
9
 
10
+ # --------------------------- الإعدادات ---------------------------
11
+ # قائمة شاملة لضمان قبول كل ملفات المشروع دون أخطاء
12
+ ALLOWED_EXTENSIONS = {
13
+ '.py', '.js', '.html', '.css', '.txt', '.json', '.md',
14
+ '.php', '.xml', '.csv', '.zip', '.png', '.jpg', '.svg', '.ico'
15
+ }
16
+ MODEL_ID = os.environ.get('HF_MODEL_ID', 'moonshotai/Kimi-K2-Instruct')
17
  HF_TOKEN = os.environ.get('HF_TOKEN')
18
 
19
+ class ProjectTank:
20
  def __init__(self):
21
+ self.work_dir = tempfile.mkdtemp(prefix="tank_dev_")
22
+ self.files_cache = {} # لتخزين محتوى الملفات: {الاسم: المحتوى}
 
 
23
 
24
+ def sync_files(self, file_objs):
25
+ """رفع الملفات وفكها وتجهيزها للمعالجة"""
26
+ if not file_objs: return "⚠️ لم يتم اختيار ملفات.", []
27
 
28
+ # تنظيف المجلد القديم
29
  if os.path.exists(self.work_dir):
30
  shutil.rmtree(self.work_dir)
31
+ self.work_dir = tempfile.mkdtemp(prefix="tank_dev_")
32
+
33
+ files = file_objs if isinstance(file_objs, list) else [file_objs]
34
+ for f in files:
35
+ name = os.path.basename(f.name)
36
+ if name.lower().endswith('.zip'):
37
  with zipfile.ZipFile(f.name, 'r') as z:
38
  z.extractall(self.work_dir)
39
  else:
40
+ dest = os.path.join(self.work_dir, name)
41
  shutil.copy(f.name, dest)
42
 
43
+ self._reload_memory()
44
+ return f"🚀 تم تحميل {len(self.files_cache)} ملف في السيرفر المحلي.", list(self.files_cache.keys())
45
+
46
+ def _reload_memory(self):
47
+ self.files_cache = {}
48
+ for p in Path(self.work_dir).rglob('*'):
49
+ if p.is_file():
50
+ rel = str(p.relative_to(self.work_dir)).replace("\\", "/")
51
+ try:
52
+ self.files_cache[rel] = p.read_text(encoding='utf-8', errors='ignore')
53
+ except: pass
54
+
55
+ def update_file_content(self, filename, content):
56
+ path = os.path.join(self.work_dir, filename)
57
+ os.makedirs(os.path.dirname(path), exist_ok=True)
58
+ with open(path, 'w', encoding='utf-8') as f:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  f.write(content)
60
+ self.files_cache[filename] = content
61
+
62
+ def build_preview(self):
63
+ """محرك المعاينة المحلي: حقن كل التبعيات في ملف واحد"""
64
+ # البحث عن نقطة الدخول (Entry Point)
65
+ entry = next((f for f in ["index.html", "main.html"] if f in self.files_cache), None)
66
+ if not entry:
67
+ entry = next((f for f in self.files_cache if f.endswith('.html')), None)
68
 
69
+ if not entry:
70
+ return "<div style='text-align:center; padding:50px;'>⚠️ لم يتم العثور على ملف HTML</div>"
71
 
72
+ html = self.files_cache[entry]
 
 
 
 
 
 
 
73
 
74
+ # حقن CSS
75
+ for name, code in self.files_cache.items():
76
+ if name.endswith('.css'):
77
+ style = f"<style data-file='{name}'>\n{code}\n</style>"
78
+ html = re.sub(f'<link.*?href=["\'].*?{name}["\'].*?>', style, html)
 
79
 
80
+ # حقن JS
81
+ for name, code in self.files_cache.items():
82
+ if name.endswith('.js'):
83
+ script = f"<script data-file='{name}'>\n{code}\n</script>"
84
+ html = re.sub(f'<script.*?src=["\'].*?{name}["\'].*?></script>', script, html)
85
 
86
+ return html
 
 
 
87
 
88
+ tank = ProjectTank()
 
89
  client = InferenceClient(api_key=HF_TOKEN) if HF_TOKEN else None
90
 
91
+ # --------------------------- منطق الذكاء الاصطناعي ---------------------------
92
 
93
+ def clean_messages(history):
94
+ """إصلاح خطأ Data Incompatible - تنظيف مصفوفة الرسائل"""
95
+ return [{"role": m["role"], "content": str(m["content"])} for m in history if "role" in m and "content" in m]
96
+
97
+ def chat_and_code(message, history):
98
  if not client:
99
+ yield history + [{"role": "assistant", "content": "⚠️ مفقود HF_TOKEN"}]
100
  return
101
 
102
+ # جمع السياق الكامل (وضع الدبابة)
103
+ context = "### CURRENT PROJECT STRUCTURE & CONTENT ###\n"
104
+ for name, content in tank.files_cache.items():
105
+ context += f"\nFILE: {name}\n{content}\n--- END ---\n"
106
+
107
+ system_instr = (
108
+ "You are an AI IDE. Provide short explanations then output the FULL modified files using this format:\n"
109
+ "[TARGET: filename]\n```\nUpdated Code\n```"
110
  )
111
 
112
+ clean_hist = clean_messages(history)
113
+ payload = [{"role": "system", "content": system_instr}]
114
+ payload.append({"role": "user", "content": f"{context}\n\nUser Request: {message}"})
 
115
 
116
+ response = ""
117
  history.append({"role": "user", "content": message})
118
  history.append({"role": "assistant", "content": ""})
119
 
120
+ stream = client.chat_completion(model=MODEL_ID, messages=payload, max_tokens=4000, stream=True)
121
+ for chunk in stream:
122
+ token = chunk.choices[0].delta.content
123
+ if token:
124
+ response += token
125
+ history[-1]["content"] = response
126
+ yield history
127
+
128
+ def apply_and_preview(history):
129
+ """استخراج الكود وتحديث السيرفر المحلي"""
130
+ if not history: return "لا توجد بيانات", None
 
 
 
 
 
 
 
 
 
131
 
132
+ content = history[-1]["content"]
133
+ matches = re.findall(r"\[TARGET:\s*(.*?)\]\s*```.*?\n(.*?)```", content, re.DOTALL)
 
 
134
 
 
 
 
 
135
  for filename, code in matches:
136
+ tank.update_file_content(filename.strip(), code.strip())
 
 
137
 
138
+ return "✅ تم التعديل والمعاينة بنجاح", tank.build_preview()
 
139
 
140
+ # --------------------------- الواجهة ---------------------------
141
 
 
 
 
 
142
 
143
+
144
+ with gr.Blocks(theme=gr.themes.Soft(), title="AI Tank IDE") as demo:
145
+ gr.Markdown("# 🛡️ AI Tank IDE Pro\n**سيرفر محلي + رؤية كاملة للملفات**")
146
+
147
  with gr.Row():
148
+ with gr.Column(scale=1):
149
+ upload = gr.UploadButton("📁 ارفع مشروعك (ZIP/Files)", file_count="multiple")
150
+ file_list = gr.Dropdown(label="ملفات المشروع المكتشفة", choices=[])
151
+ status = gr.Markdown("🟢 جاهز للعمل")
 
 
 
 
152
 
153
+ with gr.Group():
154
+ chat = gr.Chatbot(type="messages", height=400)
155
+ msg = gr.Textbox(placeholder="اطلب تعديل المشروع...", show_label=False)
156
+ with gr.Row():
157
+ send = gr.Button("إرسال", variant="primary")
158
+ apply = gr.Button(" تطبيق المعاينة", variant="stop")
 
159
 
 
160
  with gr.Column(scale=2):
161
  with gr.Tabs():
162
+ with gr.TabItem("🌐 المعاينة الفورية (Local Server)"):
163
+ preview = gr.HTML(value="<div style='text-align:center; padding:50px;'>ارفع مشروعك لرؤية النتيجة</div>")
164
+ with gr.TabItem("📝 محرر الكود"):
165
+ editor = gr.Code(label="كود الملف المختار", lines=25, interactive=True)
 
 
 
 
 
 
 
 
 
166
 
167
+ # ربط الأحداث
168
+ upload.upload(tank.sync_files, upload, [status, file_list]).then(tank.build_preview, None, preview)
 
 
169
 
170
+ send.click(chat_and_code, [msg, chat], chat).then(lambda: "", None, msg)
 
 
171
 
172
+ apply.click(apply_and_preview, chat, [status, preview])
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
+ file_list.change(lambda n: tank.files_cache.get(n, ""), file_list, editor)
 
175
 
176
  if __name__ == "__main__":
177
  demo.queue().launch()