1Egyb commited on
Commit
703674f
·
verified ·
1 Parent(s): 9917d9d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +170 -141
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
- # قائمة واسعة لضمان عدم حدوث Crash عند الرفع
12
- ALLOWED_EXTENSIONS = {
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 TankProjectManager:
20
  def __init__(self):
21
- self.work_dir = tempfile.mkdtemp(prefix="tank_ide_")
22
- self.files_memory = {} # اسم الملف: المحتوى
 
 
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="tank_ide_")
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.refresh_memory()
43
- return f"✅ تم تحميل {len(self.files_memory)} ملف بنجاح.", list(self.files_memory.keys())
44
 
45
- def refresh_memory(self):
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
- try:
52
- # قراءة كاملة للملف
53
- content = path.read_text(encoding='utf-8', errors='ignore')
54
- self.files_memory[rel_path] = content
55
- except: pass
56
-
57
- def update_file(self, filename, content):
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 get_preview_html(self):
66
- """محرك حقن التبعيات للمعاينة السريعة"""
67
- index_file = None
68
- for name in ["index.html", "main.html"]:
69
- if name in self.files_memory:
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
- # حقن الـ JS
87
- for name, js_code in self.files_memory.items():
88
- if name.endswith('.js'):
89
- script_tag = f"<script>/* {name} */\n{js_code}\n</script>"
90
- content = re.sub(f'<script.*?src=["\'].*?{name}["\'].*?></script>', script_tag, content)
91
 
92
- return content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- pm = TankProjectManager()
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 ai_handler(message, history):
114
  if not client:
115
- yield history + [{"role": "assistant", "content": "⚠️ HF_TOKEN مفقود!"}]
116
  return
117
 
118
- # بناء السياق الكامل للمشروع
119
- context = "### هيكل المشروع الحالي ومحتوى الملفات بالكامل ###\n"
120
- for name, content in pm.files_memory.items():
121
- context += f"\n--- FILE: {name} ---\n{content}\n--- END {name} ---\n"
122
-
123
  system_prompt = (
124
- "أنت مبرمج خبير (AI IDE). لقد أرسلت لك المشروع كاملاً. "
125
- "عند طلب تعديلات، أجب بالشرح ثم ضع الكود المحدث لكل ملف بالصيغة التالية:\n"
126
- "[FILE: filename.ext]\n```\nالكود المحدث هنا\n```"
 
 
 
 
127
  )
128
 
129
  messages = [{"role": "system", "content": system_prompt}]
130
- messages.extend(clean_history(history))
131
- messages.append({"role": "user", "content": f"{context}\n\nالمطلوب الآن: {message}"})
 
 
 
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=4000, stream=True)
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"❌ خطأ: {str(e)}"
147
  yield history
148
 
149
- def apply_ai_updates(history):
150
- """استخراج الكود من الشات وتحديث الـ Sandbox"""
151
- if not history: return "لا توجد بيانات", None, None
152
 
153
- text = history[-1]["content"]
154
- # البحث عن نمط الملفات [FILE: name] ```code```
155
  pattern = r"\[FILE:\s*(.*?)\]\s*```.*?\n(.*?)```"
156
- matches = re.findall(pattern, text, re.DOTALL | re.IGNORECASE)
157
 
158
  logs = []
 
 
 
159
  for filename, code in matches:
160
- filename = filename.strip()
161
- pm.update_file(filename, code.strip())
162
- logs.append(f"✅ تم تحديث {filename}")
163
 
164
- if not logs:
165
- return "⚠️ لم يتم العثور على كود بصيغة [FILE: name] لتنفيذه.", pm.get_preview_html(), list(pm.files_memory.keys())
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.Soft(), title="AI Tank IDE") as demo:
172
- gr.Markdown("# 🛡️ AI Tank IDE Pro\n**نظام برمجة متكامل: رؤية كاملة للملفات + معاينة فورية**")
173
 
174
- active_filename = gr.State()
175
 
176
  with gr.Row():
177
- # الجانب الأيسر: التحكم والملفات
178
  with gr.Column(scale=1, variant="panel"):
179
- upload_btn = gr.UploadButton("📂 رفع المشروع (ZIP/ملفات)", file_count="multiple")
180
- file_tree = gr.Dropdown(label="مستكشف الملفات", choices=[])
181
- status_log = gr.Markdown("🟢 جاهز")
 
 
 
182
 
183
- with gr.Accordion("🤖 مساعد البرمجة", open=True):
184
- chatbot = gr.Chatbot(type="messages", height=350)
185
- msg_input = gr.Textbox(placeholder="اطلب تعديلاً كبيراً على المشروع...", show_label=False)
186
- with gr.Row():
187
- send_btn = gr.Button("إرسال", variant="primary")
188
- apply_btn = gr.Button("تنفيذ التعديلات", variant="stop")
189
-
190
- # الجانب الأيمن: المحرر والمعاينة
 
191
  with gr.Column(scale=2):
192
  with gr.Tabs():
193
- with gr.TabItem("🌐 المعاينة الحية (Preview)"):
194
- preview_area = gr.HTML(value="<div style='text-align:center;'>بانتظار رفع المشروع...</div>")
195
- refresh_btn = gr.Button("🔄 تحديث المعاينة")
196
 
197
- with gr.TabItem("📝 محرر الكود"):
198
- code_editor = gr.Code(label="Source Code", lines=25, interactive=True)
199
- save_manual = gr.Button("💾 حفظ التعديل اليدوي", size="sm")
200
 
201
- download_file = gr.File(label="تحميل المشروع النهائي")
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
- upload_btn.upload(on_upload, upload_btn, [status_log, file_tree, preview_area])
212
-
213
- # 2. الشات
214
- send_btn.click(ai_handler, [msg_input, chatbot], chatbot).then(lambda: "", None, msg_input)
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
- apply_btn.click(on_apply, chatbot, [status_log, preview_area, file_tree, download_file])
223
 
224
- # 4. التبديل بين الملفات في المحرر
225
- def load_to_editor(name):
226
- return pm.files_memory.get(name, ""), name
227
 
228
- file_tree.change(load_to_editor, file_tree, [code_editor, active_filename])
 
 
 
 
229
 
230
- # 5. الحفظ اليدوي
 
 
 
 
 
231
  def on_manual_save(code, name):
232
  if not name: return "⚠️ اختر ملفاً أولاً"
233
- pm.update_file(name, code)
234
  return f"✅ تم حفظ {name} يدوياً"
 
 
235
 
236
- save_manual.click(on_manual_save, [code_editor, active_filename], status_log)
237
- refresh_btn.click(pm.get_preview_html, None, preview_area)
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()