1Egyb commited on
Commit
fc829bc
·
verified ·
1 Parent(s): b46d003

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +54 -225
app.py CHANGED
@@ -6,27 +6,20 @@ import zipfile
6
  from pathlib import Path
7
  import gradio as gr
8
  from huggingface_hub import InferenceClient
9
- from flask import Flask, send_from_directory, render_template_string, request
10
- from watchdog.observers import Observer
11
- from watchdog.events import FileSystemEventHandler
12
- import tornado.ioloop
13
- import tornado.web
14
- import tornado.websocket
15
 
16
  # --------------------------- الإعدادات ---------------------------
17
  ALLOWED_EXTENSIONS = {
18
  '.py', '.js', '.html', '.css', '.txt', '.json', '.md',
19
  '.php', '.xml', '.csv', '.zip', '.png', '.jpg', '.svg', '.ico'
20
  }
21
- # نستخدم نموذج Qwen2.5-Coder لقوته الفائقة في فهم المشاريع البرمجية كاملة
22
- MODEL_ID = os.environ.get('HF_MODEL_ID', 'Qwen/Qwen2.5-Coder-32B-Instruct')
23
  HF_TOKEN = os.environ.get('HF_TOKEN')
24
 
25
  class ProjectTank:
26
  def __init__(self):
27
  self.work_dir = tempfile.mkdtemp(prefix="tank_dev_")
28
- self.files_cache = {} # {filename: content}
29
- self.structure = "" # خريطة المشروع
30
 
31
  def sync_files(self, file_objs):
32
  if not file_objs: return "⚠️ لم يتم اختيار ملفات.", []
@@ -46,7 +39,6 @@ class ProjectTank:
46
  shutil.copy(f.name, dest)
47
 
48
  self._reload_memory()
49
- self._start_live_preview()
50
  return f"🚀 تم تحميل {len(self.files_cache)} ملف بنجاح.", list(self.files_cache.keys())
51
 
52
  def _reload_memory(self):
@@ -57,8 +49,7 @@ class ProjectTank:
57
  rel = str(p.relative_to(self.work_dir)).replace("\\", "/")
58
  structure_list.append(f"- {rel}")
59
  try:
60
- # نقرأ فقط ملفات الكود في الذاكرة لتوفير الـ Tokens
61
- if p.suffix in ['.html', '.css', '.js', '.json', '.txt', '.md', '.py']:
62
  self.files_cache[rel] = p.read_text(encoding='utf-8', errors='ignore')
63
  except: pass
64
  self.structure = "\n".join(structure_list)
@@ -71,87 +62,31 @@ class ProjectTank:
71
  self.files_cache[filename] = content
72
 
73
  def build_preview(self):
74
- entry = next((f for f in ["index.html", "main.html"] if f in self.files_cache),
75
- next((f for f in self.files_cache if f.endswith('.html')), None))
 
 
76
 
77
  if not entry:
78
- return "<div style='text-align:center; padding:50px;'>⚠️ لم يتم العثور على ملف HTML للمعاينة</div>"
 
 
 
 
79
 
80
  html = self.files_cache[entry]
 
81
 
82
- # 1. حقن CSS (بذكاء: استبدال أو إضافة في الـ Head)
83
- css_content = ""
84
  for name, code in self.files_cache.items():
 
85
  if name.endswith('.css'):
86
- css_content += f"\n/* --- {name} --- */\n{code}\n"
87
-
88
- if "</head>" in html:
89
- html = html.replace("</head>", f"<style>{css_content}</style>\n</head>")
90
- else:
91
- html = f"<style>{css_content}</style>" + html
92
-
93
- # 2. حقن JS (في نهاية الـ Body لضمان عمل الـ DOM)
94
- js_content = ""
95
- for name, code in self.files_cache.items():
96
  if name.endswith('.js'):
97
- js_content += f"\n// --- {name} ---\n{code}\n"
98
-
99
- if "</body>" in html:
100
- html = html.replace("</body>", f"<script>{js_content}</script>\n</body>")
101
- else:
102
- html += f"<script>{js_content}</script>"
103
 
104
  return html
105
 
106
- def export_project(self):
107
- out_path = tempfile.mktemp()
108
- shutil.make_archive(out_path, 'zip', self.work_dir)
109
- return out_path + ".zip"
110
-
111
- def _start_live_preview(self):
112
- class PreviewHandler(tornado.web.RequestHandler):
113
- def get(self):
114
- entry = next((f for f in ["index.html", "main.html"] if f in tank.files_cache),
115
- next((f for f in tank.files_cache if f.endswith('.html')), None))
116
- if not entry:
117
- self.write("<div style='text-align:center; padding:50px;'>⚠️ لم يتم العثور على ملف HTML للمعاينة</div>")
118
- return
119
- self.write(tank.files_cache[entry])
120
-
121
- class WebSocketHandler(tornado.websocket.WebSocketHandler):
122
- clients = set()
123
-
124
- def open(self):
125
- WebSocketHandler.clients.add(self)
126
-
127
- def on_close(self):
128
- WebSocketHandler.clients.remove(self)
129
-
130
- @classmethod
131
- def send_update(cls, message):
132
- for client in cls.clients:
133
- client.write_message(message)
134
-
135
- class Watcher(FileSystemEventHandler):
136
- def on_modified(self, event):
137
- if event.is_directory: return
138
- if Path(event.src_path).suffix in ALLOWED_EXTENSIONS:
139
- tank._reload_memory()
140
- message = tank.build_preview()
141
- WebSocketHandler.send_update(message)
142
-
143
- self.watcher = Observer()
144
- self.watcher.schedule(Watcher(), self.work_dir, recursive=True)
145
- self.watcher.start()
146
-
147
- self.server = tornado.web.Application([
148
- (r"/preview", PreviewHandler),
149
- (r"/ws", WebSocketHandler),
150
- ])
151
- self.server.listen(8888)
152
- tornado.ioloop.IOLoop.current().start()
153
-
154
-
155
  tank = ProjectTank()
156
  client = InferenceClient(api_key=HF_TOKEN) if HF_TOKEN else None
157
 
@@ -162,25 +97,19 @@ def chat_and_code(message, history):
162
  yield history + [{"role": "assistant", "content": "⚠️ مفقود HF_TOKEN"}]
163
  return
164
 
165
- # بناء سياق قوي: الهيكل + المحتوى
166
- context = f"### PROJECT STRUCTURE ###\n{tank.structure}\n\n"
167
- context += "### FULL FILE CONTENTS ###\n"
168
  for name, content in tank.files_cache.items():
169
  context += f"--- FILE: {name} ---\n{content}\n"
170
 
171
  system_instr = (
172
- "You are an expert AI IDE. Your goal is to help the user build and debug their project.\n"
173
- "1. Analyze the project structure to understand how files interact.\n"
174
- "2. Provide clear explanations.\n"
175
- "3. When modifying code, use the format: [TARGET: filename] followed by a code block.\n"
176
- "4. Always output the FULL content of the file in the code block."
177
  )
178
 
179
  messages = [{"role": "system", "content": system_instr}]
180
  for m in history:
181
  messages.append({"role": m["role"], "content": str(m["content"])})
182
-
183
- messages.append({"role": "user", "content": f"{context}\n\nTask: {message}"})
184
 
185
  response = ""
186
  history.append({"role": "user", "content": message})
@@ -189,157 +118,57 @@ def chat_and_code(message, history):
189
  try:
190
  stream = client.chat_completion(model=MODEL_ID, messages=messages, max_tokens=6000, stream=True)
191
  for chunk in stream:
192
- if hasattr(chunk, 'choices') and chunk.choices and len(chunk.choices) > 0:
193
  token = chunk.choices[0].delta.content
194
  if token:
195
  response += token
196
  history[-1]["content"] = response
197
  yield history
198
  except Exception as e:
199
- history[-1]["content"] = f"❌ خطأ في السيرفر: {str(e)}"
200
  yield history
201
 
202
  def apply_and_preview(history):
203
  if not history: return "لا توجد بيانات", None, []
204
-
205
  content = history[-1]["content"]
206
  matches = re.findall(r"\[TARGET:\s*(.*?)\]\s*```.*?\n(.*?)```", content, re.DOTALL | re.IGNORECASE)
207
 
208
- if not matches:
209
- return "⚠️ لم يتم اكتشاف ملفات للتحديث.", tank.build_preview(), list(tank.files_cache.keys())
210
-
211
  for filename, code in matches:
212
  tank.update_file_content(filename.strip(), code.strip())
213
 
214
- return "✅ تم تحديث الملفات وحقن المعاينة بنجاح!", tank.build_preview(), list(tank.files_cache.keys())
215
-
216
- # --------------------------- الواجهة ---------------------------
217
-
218
- app = Flask(__name__)
219
-
220
- @app.route('/upload', methods=['POST'])
221
- def upload_file():
222
- files = request.files.getlist('files[]')
223
- log, choices = tank.sync_files(files)
224
- return log
225
-
226
- @app.route('/files/<path:filename>')
227
- def get_file(filename):
228
- path = os.path.join(tank.work_dir, filename)
229
- return send_from_directory(tank.work_dir, filename)
230
 
231
- @app.route('/project')
232
- def get_project_structure():
233
- return {"structure": tank.structure}
234
 
235
- @app.route('/preview')
236
- def serve_preview():
237
- entry = next((f for f in ["index.html", "main.html"] if f in tank.files_cache),
238
- next((f for f in tank.files_cache if f.endswith('.html')), None))
239
- if not entry:
240
- return "⚠️ لم يتم العثور على ملف HTML للمعاينة"
241
- return render_template_string(tank.files_cache[entry])
242
-
243
- @app.route('/chat', methods=['POST'])
244
- def chat_endpoint():
245
- message = request.form['message']
246
- history = request.form.get('history', [])
247
- if not client:
248
- history.append({"role": "assistant", "content": "⚠️ مفقود HF_TOKEN"})
249
- return str(history)
250
 
251
- context = f"### PROJECT STRUCTURE ###\n{tank.structure}\n\n"
252
- context += "### FULL FILE CONTENTS ###\n"
253
- for name, content in tank.files_cache.items():
254
- context += f"--- FILE: {name} ---\n{content}\n"
255
-
256
- system_instr = (
257
- "You are an expert AI IDE. Your goal is to help the user build and debug their project.\n"
258
- "1. Analyze the project structure to understand how files interact.\n"
259
- "2. Provide clear explanations.\n"
260
- "3. When modifying code, use the format: [TARGET: filename] followed by a code block.\n"
261
- "4. Always output the FULL content of the file in the code block."
262
- )
263
-
264
- messages = [{"role": "system", "content": system_instr}]
265
- for m in history:
266
- messages.append({"role": m["role"], "content": str(m)})
267
-
268
- messages.append({"role": "user", "content": f"{context}\n\nTask: {message}"})
269
-
270
- response = ""
271
- history.append({"role": "user", "content": message})
272
- history.append({"role": "assistant", "content": ""})
273
-
274
- try:
275
- stream = client.chat_completion(model=MODEL_ID, messages=messages, max_tokens=6000, stream=True)
276
- for chunk in stream:
277
- if hasattr(chunk, 'choices') and chunk.choices and len(chunk.choices) > 0:
278
- token = chunk.choices[0].delta.content
279
- if token:
280
- response += token
281
- history[-1]["content"] = response
282
- return str(history)
283
- except Exception as e:
284
- history[-1]["content"] = f"❌ خطأ في السيرفر: {str(e)}"
285
- return str(history)
286
-
287
- @app.route('/apply', methods=['POST'])
288
- def apply_endpoint():
289
- history = request.form.get('history', [])
290
- if not history: return "لا توجد بيانات"
291
-
292
- content = history[-1]["content"]
293
- matches = re.findall(r"\[TARGET:\s*(.*?)\]\s*```.*?\n(.*?)```", content, re.DOTALL | re.IGNORECASE)
294
-
295
- if not matches:
296
- return "⚠️ لم يتم اكتشاف ملفات للتحديث."
297
-
298
- for filename, code in matches:
299
- tank.update_file_content(filename.strip(), code.strip())
300
-
301
- return "✅ تم تحديث الملفات وحقن المعاينة بنجاح!"
302
-
303
- @app.route('/export')
304
- def export_project():
305
- return send_from_directory(Path(tank.export_project()).parent, Path(tank.export_project()).name)
306
-
307
- @app.route('/')
308
- def serve_index():
309
- return """
310
- <div>
311
- <h1>AI Tank IDE Pro</h1>
312
- <a href="/preview">معاينة المشروع</a><br/>
313
- <input type="file" id="files" name="files[]" multiple />
314
- <button onclick="uploadFiles()">ارفع المشروع</button>
315
- <div id="status">جاهز للعمل</div>
316
- <div id="preview" style="margin-top: 20px;"></div>
317
- </div>
318
- <script>
319
- function uploadFiles() {
320
- const formData = new FormData();
321
- const fileInput = document.getElementById('files');
322
- Array.from(fileInput.files).forEach(file => {
323
- formData.append('files[]', file);
324
- });
325
 
326
- fetch('/upload', { method: 'POST', body: formData })
327
- .then(response => response.text())
328
- .then(log => {
329
- document.getElementById('status').innerText = log;
330
- if (log.includes('تم تحميل')) {
331
- fetch('/preview')
332
- .then(res => res.text())
333
- .then(html => {
334
- document.getElementById('preview').innerHTML = html;
335
- });
336
- }
337
- });
338
- }
339
- </script>
340
- """
 
 
 
341
 
342
  if __name__ == "__main__":
343
- tank = ProjectTank()
344
- client = InferenceClient(api_key=HF_TOKEN) if HF_TOKEN else None
345
- app.run(host='0.0.0.0', port=7860)
 
6
  from pathlib import Path
7
  import gradio as gr
8
  from huggingface_hub import InferenceClient
 
 
 
 
 
 
9
 
10
  # --------------------------- الإعدادات ---------------------------
11
  ALLOWED_EXTENSIONS = {
12
  '.py', '.js', '.html', '.css', '.txt', '.json', '.md',
13
  '.php', '.xml', '.csv', '.zip', '.png', '.jpg', '.svg', '.ico'
14
  }
15
+ MODEL_ID = os.environ.get('HF_MODEL_ID', 'Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8')
 
16
  HF_TOKEN = os.environ.get('HF_TOKEN')
17
 
18
  class ProjectTank:
19
  def __init__(self):
20
  self.work_dir = tempfile.mkdtemp(prefix="tank_dev_")
21
+ self.files_cache = {}
22
+ self.structure = ""
23
 
24
  def sync_files(self, file_objs):
25
  if not file_objs: return "⚠️ لم يتم اختيار ملفات.", []
 
39
  shutil.copy(f.name, dest)
40
 
41
  self._reload_memory()
 
42
  return f"🚀 تم تحميل {len(self.files_cache)} ملف بنجاح.", list(self.files_cache.keys())
43
 
44
  def _reload_memory(self):
 
49
  rel = str(p.relative_to(self.work_dir)).replace("\\", "/")
50
  structure_list.append(f"- {rel}")
51
  try:
52
+ if p.suffix.lower() in ['.html', '.css', '.js', '.json', '.txt', '.md', '.py']:
 
53
  self.files_cache[rel] = p.read_text(encoding='utf-8', errors='ignore')
54
  except: pass
55
  self.structure = "\n".join(structure_list)
 
62
  self.files_cache[filename] = content
63
 
64
  def build_preview(self):
65
+ """رادار ذكي للبحث عن ملف التشغيل في أي عمق"""
66
+ all_keys = list(self.files_cache.keys())
67
+ # البحث عن أي ملف ينتهي بـ index.html بغض النظر عن المجلد
68
+ entry = next((k for k in all_keys if k.lower().endswith('index.html')), None)
69
 
70
  if not entry:
71
+ # إذا لم يجد index، يبحث عن أي ملف html
72
+ entry = next((k for k in all_keys if k.endswith('.html')), None)
73
+
74
+ if not entry:
75
+ return f"<div style='text-align:center; padding:50px;'>⚠️ لم يتم العثور على ملف HTML للمعاينة.<br>الملفات المكتشفة: {str(all_keys[:3])}</div>"
76
 
77
  html = self.files_cache[entry]
78
+ base_dir = os.path.dirname(entry)
79
 
80
+ # حقن CSS و JS مع مراعاة المسارات
 
81
  for name, code in self.files_cache.items():
82
+ fname = os.path.basename(name)
83
  if name.endswith('.css'):
84
+ html = re.sub(rf'<link.*?href=["\'].*?{fname}["\'].*?>', f"<style>\n{code}\n</style>", html)
 
 
 
 
 
 
 
 
 
85
  if name.endswith('.js'):
86
+ html = re.sub(rf'<script.*?src=["\'].*?{fname}["\'].*?></script>', f"<script>\n{code}\n</script>", html)
 
 
 
 
 
87
 
88
  return html
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  tank = ProjectTank()
91
  client = InferenceClient(api_key=HF_TOKEN) if HF_TOKEN else None
92
 
 
97
  yield history + [{"role": "assistant", "content": "⚠️ مفقود HF_TOKEN"}]
98
  return
99
 
100
+ context = f"### PROJECT STRUCTURE ###\n{tank.structure}\n\n### FULL FILE CONTENTS ###\n"
 
 
101
  for name, content in tank.files_cache.items():
102
  context += f"--- FILE: {name} ---\n{content}\n"
103
 
104
  system_instr = (
105
+ "You are an AI IDE. Provide short explanations then output the FULL modified files using this format:\n"
106
+ "[TARGET: filename]\n```\nUpdated Code\n```"
 
 
 
107
  )
108
 
109
  messages = [{"role": "system", "content": system_instr}]
110
  for m in history:
111
  messages.append({"role": m["role"], "content": str(m["content"])})
112
+ messages.append({"role": "user", "content": f"{context}\n\nUser Request: {message}"})
 
113
 
114
  response = ""
115
  history.append({"role": "user", "content": message})
 
118
  try:
119
  stream = client.chat_completion(model=MODEL_ID, messages=messages, max_tokens=6000, stream=True)
120
  for chunk in stream:
121
+ if hasattr(chunk, 'choices') and chunk.choices:
122
  token = chunk.choices[0].delta.content
123
  if token:
124
  response += token
125
  history[-1]["content"] = response
126
  yield history
127
  except Exception as e:
128
+ history[-1]["content"] = f"❌ خطأ: {str(e)}"
129
  yield history
130
 
131
  def apply_and_preview(history):
132
  if not history: return "لا توجد بيانات", None, []
 
133
  content = history[-1]["content"]
134
  matches = re.findall(r"\[TARGET:\s*(.*?)\]\s*```.*?\n(.*?)```", content, re.DOTALL | re.IGNORECASE)
135
 
 
 
 
136
  for filename, code in matches:
137
  tank.update_file_content(filename.strip(), code.strip())
138
 
139
+ tank._reload_memory()
140
+ return "✅ تم التحديث بنجاح!", tank.build_preview(), list(tank.files_cache.keys())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
+ # --------------------------- الواجهة (Gradio هي الأضمن في الاستقرار) ---------------------------
 
 
143
 
144
+ with gr.Blocks(theme=gr.themes.Monochrome(), title="AI Tank IDE Pro") 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="ملفات المشروع")
151
+ status = gr.Markdown("🟢 جاهز")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
+ chat = gr.Chatbot(type="messages", height=400)
154
+ msg = gr.Textbox(placeholder="اطلب تعديلاً...", show_label=False)
155
+ with gr.Row():
156
+ send = gr.Button("إرسال", variant="primary")
157
+ apply = gr.Button("⚡ تطبيق المعاينة", variant="stop")
158
+
159
+ with gr.Column(scale=2):
160
+ with gr.Tabs():
161
+ with gr.TabItem("🌐 المعاينة الفورية"):
162
+ preview = gr.HTML()
163
+ with gr.TabItem("📝 المحرر"):
164
+ editor = gr.Code(label="كود الملف", lines=25, interactive=True)
165
+
166
+ # ربط الأحداث
167
+ upload.upload(tank.sync_files, upload, [status, file_list]).then(tank.build_preview, None, preview)
168
+ send.click(chat_and_code, [msg, chat], chat).then(lambda: "", None, msg)
169
+ apply.click(apply_and_preview, chat, [status, preview, file_list])
170
+ file_list.change(lambda n: tank.files_cache.get(n, ""), file_list, editor)
171
 
172
  if __name__ == "__main__":
173
+ demo.queue().launch(server_name="0.0.0.0", server_port=7860)
174
+ port=7860)