Update app.py
Browse files
app.py
CHANGED
|
@@ -7,28 +7,26 @@ from pathlib import Path
|
|
| 7 |
import gradio as gr
|
| 8 |
from huggingface_hub import InferenceClient
|
| 9 |
|
| 10 |
-
# ---------------------------
|
| 11 |
-
#
|
| 12 |
-
|
| 13 |
-
'.py', '.js', '.html', '.css', '.txt', '.json', '.md',
|
| 14 |
-
'.php', '.xml', '.csv', '.zip', '.png', '.jpg', '.svg'
|
| 15 |
-
}
|
| 16 |
-
MODEL_ID = os.environ.get('HF_MODEL_ID', 'deepseek-ai/DeepSeek-V3.2')
|
| 17 |
HF_TOKEN = os.environ.get('HF_TOKEN')
|
| 18 |
|
| 19 |
-
class
|
| 20 |
def __init__(self):
|
| 21 |
-
|
| 22 |
-
self.
|
|
|
|
|
|
|
| 23 |
|
| 24 |
def load_project(self, files):
|
| 25 |
-
"""تحميل
|
| 26 |
-
if not files: return "⚠️
|
| 27 |
|
| 28 |
-
# تنظيف
|
| 29 |
if os.path.exists(self.work_dir):
|
| 30 |
shutil.rmtree(self.work_dir)
|
| 31 |
-
self.work_dir = tempfile.mkdtemp(prefix="
|
| 32 |
|
| 33 |
file_list = files if isinstance(files, list) else [files]
|
| 34 |
for f in file_list:
|
|
@@ -39,103 +37,134 @@ class TankProjectManager:
|
|
| 39 |
dest = os.path.join(self.work_dir, os.path.basename(f.name))
|
| 40 |
shutil.copy(f.name, dest)
|
| 41 |
|
| 42 |
-
self.
|
| 43 |
-
return f"✅ تم تحميل {len(self.files_memory)}
|
| 44 |
|
| 45 |
-
def
|
| 46 |
-
"""
|
| 47 |
self.files_memory = {}
|
|
|
|
|
|
|
|
|
|
| 48 |
for path in Path(self.work_dir).rglob('*'):
|
| 49 |
-
if path.is_file():
|
| 50 |
rel_path = str(path.relative_to(self.work_dir)).replace("\\", "/")
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
full_path = os.path.join(self.work_dir, filename)
|
| 60 |
os.makedirs(os.path.dirname(full_path), exist_ok=True)
|
| 61 |
with open(full_path, 'w', encoding='utf-8') as f:
|
| 62 |
f.write(content)
|
| 63 |
self.files_memory[filename] = content
|
| 64 |
|
| 65 |
-
def
|
| 66 |
-
"""
|
| 67 |
-
|
| 68 |
-
for
|
| 69 |
-
|
| 70 |
-
index_file = name
|
| 71 |
-
break
|
| 72 |
-
if not index_file:
|
| 73 |
-
# البحث عن أول ملف html متاح
|
| 74 |
-
html_files = [f for f in self.files_memory if f.endswith('.html')]
|
| 75 |
-
if not html_files: return "<div style='text-align:center;padding:20px;'>⚠️ لا يوجد ملف HTML للمعاينة</div>"
|
| 76 |
-
index_file = html_files[0]
|
| 77 |
-
|
| 78 |
-
content = self.files_memory[index_file]
|
| 79 |
-
|
| 80 |
-
# حقن الـ CSS
|
| 81 |
-
for name, css_code in self.files_memory.items():
|
| 82 |
-
if name.endswith('.css'):
|
| 83 |
-
style_tag = f"<style>/* {name} */\n{css_code}\n</style>"
|
| 84 |
-
content = re.sub(f'<link.*?href=["\'].*?{name}["\'].*?>', style_tag, content)
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
content = re.sub(f'<script.*?src=["\'].*?{name}["\'].*?></script>', script_tag, content)
|
| 91 |
|
| 92 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
def export_zip(self):
|
| 95 |
out = tempfile.mktemp()
|
| 96 |
shutil.make_archive(out, 'zip', self.work_dir)
|
| 97 |
return out + ".zip"
|
| 98 |
|
| 99 |
-
# تهيئة
|
| 100 |
-
|
| 101 |
client = InferenceClient(api_key=HF_TOKEN) if HF_TOKEN else None
|
| 102 |
|
| 103 |
-
# ---------------------------
|
| 104 |
-
|
| 105 |
-
def clean_history(history):
|
| 106 |
-
"""إصلاح خطأ Data incompatible عبر تنظيف القائمة"""
|
| 107 |
-
cleaned = []
|
| 108 |
-
for msg in history:
|
| 109 |
-
if isinstance(msg, dict) and "role" in msg and "content" in msg:
|
| 110 |
-
cleaned.append({"role": msg["role"], "content": str(msg["content"])})
|
| 111 |
-
return cleaned
|
| 112 |
|
| 113 |
-
def
|
| 114 |
if not client:
|
| 115 |
-
yield history + [{"role": "assistant", "content": "⚠️ HF_TOKEN
|
| 116 |
return
|
| 117 |
|
| 118 |
-
#
|
| 119 |
-
context =
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
system_prompt = (
|
| 124 |
-
"أنت
|
| 125 |
-
"
|
| 126 |
-
"[FILE: filename
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
)
|
| 128 |
|
| 129 |
messages = [{"role": "system", "content": system_prompt}]
|
| 130 |
-
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
| 132 |
|
|
|
|
| 133 |
full_response = ""
|
| 134 |
history.append({"role": "user", "content": message})
|
| 135 |
history.append({"role": "assistant", "content": ""})
|
| 136 |
|
| 137 |
try:
|
| 138 |
-
stream = client.chat_completion(model=MODEL_ID, messages=messages, max_tokens=
|
| 139 |
for chunk in stream:
|
| 140 |
token = chunk.choices[0].delta.content
|
| 141 |
if token:
|
|
@@ -143,98 +172,98 @@ def ai_handler(message, history):
|
|
| 143 |
history[-1]["content"] = full_response
|
| 144 |
yield history
|
| 145 |
except Exception as e:
|
| 146 |
-
history[-1]["content"] = f"❌
|
| 147 |
yield history
|
| 148 |
|
| 149 |
-
def
|
| 150 |
-
"""
|
| 151 |
-
if not history: return "لا
|
| 152 |
|
| 153 |
-
|
| 154 |
-
#
|
| 155 |
pattern = r"\[FILE:\s*(.*?)\]\s*```.*?\n(.*?)```"
|
| 156 |
-
matches = re.findall(pattern,
|
| 157 |
|
| 158 |
logs = []
|
|
|
|
|
|
|
|
|
|
| 159 |
for filename, code in matches:
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
logs.append(f"✅ تم
|
| 163 |
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
return "\n".join(logs), pm.get_preview_html(), list(pm.files_memory.keys())
|
| 168 |
|
| 169 |
-
# --------------------------- واجهة
|
| 170 |
|
| 171 |
-
with gr.Blocks(theme=gr.themes.
|
| 172 |
-
gr.Markdown("# 🛡️
|
| 173 |
|
| 174 |
-
|
| 175 |
|
| 176 |
with gr.Row():
|
| 177 |
-
#
|
| 178 |
with gr.Column(scale=1, variant="panel"):
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
|
|
|
| 191 |
with gr.Column(scale=2):
|
| 192 |
with gr.Tabs():
|
| 193 |
-
with gr.TabItem("🌐 المعاينة الحية
|
| 194 |
-
|
| 195 |
-
refresh_btn = gr.Button("🔄 تحديث المعاينة")
|
| 196 |
|
| 197 |
-
with gr.TabItem("
|
| 198 |
-
|
| 199 |
-
|
| 200 |
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
# --- ربط الأحداث ---
|
| 204 |
-
|
| 205 |
-
# 1. الرفع
|
| 206 |
-
def on_upload(files):
|
| 207 |
-
msg, files_list = pm.load_project(files)
|
| 208 |
-
preview = pm.get_preview_html()
|
| 209 |
-
return msg, gr.update(choices=files_list), preview
|
| 210 |
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
# 3. تنفيذ تعديلات AI
|
| 217 |
-
def on_apply(h):
|
| 218 |
-
log, preview, files_list = apply_ai_updates(h)
|
| 219 |
-
zip_path = pm.export_zip()
|
| 220 |
-
return log, preview, gr.update(choices=files_list), zip_path
|
| 221 |
|
| 222 |
-
|
| 223 |
|
| 224 |
-
#
|
| 225 |
-
|
| 226 |
-
return pm.files_memory.get(name, ""), name
|
| 227 |
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
def on_manual_save(code, name):
|
| 232 |
if not name: return "⚠️ اختر ملفاً أولاً"
|
| 233 |
-
|
| 234 |
return f"✅ تم حفظ {name} يدوياً"
|
|
|
|
|
|
|
| 235 |
|
| 236 |
-
|
| 237 |
-
|
| 238 |
|
| 239 |
if __name__ == "__main__":
|
| 240 |
demo.queue().launch()
|
|
|
|
| 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:
|
|
|
|
| 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 |
+
# 1. التجهيز
|
| 141 |
+
context = tank.get_context(message)
|
| 142 |
+
|
| 143 |
+
# 2. التوجيه الصارم (The Strict System Prompt)
|
|
|
|
| 144 |
system_prompt = (
|
| 145 |
+
"أنت 'Tank Engine'، بيئة تطوير قوية (IDE). هدفك: كتابة كود يعمل (Functional Code) وليس مجرد نصوص.\n"
|
| 146 |
+
"القواعد الصارمة:\n"
|
| 147 |
+
"1. المخرجات: يجب أن تكون بصيغة [FILE: filename] يليها بلوك الكود.\n"
|
| 148 |
+
"2. التبعيات: لا يوجد NPM. إذا احتجت مكتبة (React, Vue, Tailwind, Chart.js)، استخدم رابط CDN في كود HTML فوراً.\n"
|
| 149 |
+
"3. الشمولية: عند تعديل ملف، أعد كتابته كاملاً. لا تستخدم عبارات مثل '...بقية الكود...'.\n"
|
| 150 |
+
"4. التصميم: استخدم Tailwind CSS عبر CDN إذا طلب منك تصميم عصري، إلا إذا طلب المستخدم CSS مخصص.\n"
|
| 151 |
+
"5. لا تعتذر ولا تشرح كثيراً. اكتب الكود."
|
| 152 |
)
|
| 153 |
|
| 154 |
messages = [{"role": "system", "content": system_prompt}]
|
| 155 |
+
# تنظيف التاريخ لتجنب الأخطاء
|
| 156 |
+
for msg in history:
|
| 157 |
+
if isinstance(msg, dict): messages.append(msg)
|
| 158 |
+
|
| 159 |
+
messages.append({"role": "user", "content": f"{context}\n\nTask: {message}"})
|
| 160 |
|
| 161 |
+
# 3. التنفيذ
|
| 162 |
full_response = ""
|
| 163 |
history.append({"role": "user", "content": message})
|
| 164 |
history.append({"role": "assistant", "content": ""})
|
| 165 |
|
| 166 |
try:
|
| 167 |
+
stream = client.chat_completion(model=MODEL_ID, messages=messages, max_tokens=8000, stream=True)
|
| 168 |
for chunk in stream:
|
| 169 |
token = chunk.choices[0].delta.content
|
| 170 |
if token:
|
|
|
|
| 172 |
history[-1]["content"] = full_response
|
| 173 |
yield history
|
| 174 |
except Exception as e:
|
| 175 |
+
history[-1]["content"] = f"❌ فشل المعالجة: {str(e)}"
|
| 176 |
yield history
|
| 177 |
|
| 178 |
+
def apply_updates(history):
|
| 179 |
+
"""تنفيذ التعديلات واستخراج الملفات"""
|
| 180 |
+
if not history: return "لا يوجد بيانات", None, None
|
| 181 |
|
| 182 |
+
raw_text = history[-1]["content"]
|
| 183 |
+
# نمط استخراج قوي
|
| 184 |
pattern = r"\[FILE:\s*(.*?)\]\s*```.*?\n(.*?)```"
|
| 185 |
+
matches = re.findall(pattern, raw_text, re.DOTALL | re.IGNORECASE)
|
| 186 |
|
| 187 |
logs = []
|
| 188 |
+
if not matches:
|
| 189 |
+
return "⚠️ لم يتم العثور على كود قابل للتنفيذ.", tank.compile_preview(), list(tank.files_memory.keys())
|
| 190 |
+
|
| 191 |
for filename, code in matches:
|
| 192 |
+
clean_name = filename.strip()
|
| 193 |
+
tank.force_update(clean_name, code.strip())
|
| 194 |
+
logs.append(f"✅ تم تحديث: {clean_name}")
|
| 195 |
|
| 196 |
+
tank.sync_system() # تحديث الخريطة
|
| 197 |
+
return "\n".join(logs), tank.compile_preview(), list(tank.files_memory.keys())
|
|
|
|
|
|
|
| 198 |
|
| 199 |
+
# --------------------------- واجهة التحكم (Cockpit) ---------------------------
|
| 200 |
|
| 201 |
+
with gr.Blocks(theme=gr.themes.Monochrome(), title="Tank IDE - Power Edition") as demo:
|
| 202 |
+
gr.Markdown("# 🛡️ Tank IDE <span style='font-size:0.6em; opacity:0.5'>Power Edition</span>")
|
| 203 |
|
| 204 |
+
active_file = gr.State()
|
| 205 |
|
| 206 |
with gr.Row():
|
| 207 |
+
# العمود الأيسر: التحكم والملفات
|
| 208 |
with gr.Column(scale=1, variant="panel"):
|
| 209 |
+
with gr.Group():
|
| 210 |
+
upload = gr.UploadButton("📂 تحميل المشروع (ZIP)", file_count="multiple", variant="secondary")
|
| 211 |
+
file_dropdown = gr.Dropdown(label="ملفات المشروع", interactive=True)
|
| 212 |
+
download = gr.DownloadButton("💾 تصدير المشروع (ZIP)")
|
| 213 |
+
|
| 214 |
+
status = gr.Markdown("جاهز للعمل.")
|
| 215 |
|
| 216 |
+
# الشات - العقل المدبر
|
| 217 |
+
chatbot = gr.Chatbot(height=500, type="messages", show_label=False)
|
| 218 |
+
msg = gr.Textbox(placeholder="اكتب الأمر البرمجي هنا (مثلاً: أضف مكتبة Charts واعرض بيانات المبيعات)", show_label=False)
|
| 219 |
+
|
| 220 |
+
with gr.Row():
|
| 221 |
+
btn_send = gr.Button("تنفيذ الأمر", variant="primary")
|
| 222 |
+
btn_apply = gr.Button("تطبيق التغييرات ⚡", variant="stop")
|
| 223 |
+
|
| 224 |
+
# العمود الأيمن: المعاينة والكود
|
| 225 |
with gr.Column(scale=2):
|
| 226 |
with gr.Tabs():
|
| 227 |
+
with gr.TabItem("🌐 المعاينة الحية"):
|
| 228 |
+
preview = gr.HTML(label="المخرج النهائي", min_height=600)
|
|
|
|
| 229 |
|
| 230 |
+
with gr.TabItem("💻 الكود المصدري"):
|
| 231 |
+
editor = gr.Code(label="محرر الملفات", language="python", lines=30, interactive=True)
|
| 232 |
+
btn_save_manual = gr.Button("حفظ تعديل يدوي", size="sm")
|
| 233 |
|
| 234 |
+
# --------------------------- التشبيك (Wiring) ---------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
|
| 236 |
+
# 1. التحميل
|
| 237 |
+
def on_load(f):
|
| 238 |
+
log, files = tank.load_project(f)
|
| 239 |
+
return log, gr.update(choices=files), tank.compile_preview()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
|
| 241 |
+
upload.upload(on_load, upload, [status, file_dropdown, preview])
|
| 242 |
|
| 243 |
+
# 2. الشات والتنفيذ
|
| 244 |
+
btn_send.click(engine_response, [msg, chatbot], chatbot).then(lambda: "", None, msg)
|
|
|
|
| 245 |
|
| 246 |
+
def on_apply_click(h):
|
| 247 |
+
log, html, files = apply_updates(h)
|
| 248 |
+
return log, html, gr.update(choices=files)
|
| 249 |
+
|
| 250 |
+
btn_apply.click(on_apply_click, chatbot, [status, preview, file_dropdown])
|
| 251 |
|
| 252 |
+
# 3. عرض الملفات وتعديلها يدوياً
|
| 253 |
+
def on_select_file(f):
|
| 254 |
+
return tank.files_memory.get(f, ""), f
|
| 255 |
+
|
| 256 |
+
file_dropdown.change(on_select_file, file_dropdown, [editor, active_file])
|
| 257 |
+
|
| 258 |
def on_manual_save(code, name):
|
| 259 |
if not name: return "⚠️ اختر ملفاً أولاً"
|
| 260 |
+
tank.force_update(name, code)
|
| 261 |
return f"✅ تم حفظ {name} يدوياً"
|
| 262 |
+
|
| 263 |
+
btn_save_manual.click(on_manual_save, [editor, active_file], status).then(tank.compile_preview, None, preview)
|
| 264 |
|
| 265 |
+
# 4. التصدير
|
| 266 |
+
download.click(tank.export_zip, None, download)
|
| 267 |
|
| 268 |
if __name__ == "__main__":
|
| 269 |
demo.queue().launch()
|