1Egyb commited on
Commit
7e014a3
·
verified ·
1 Parent(s): 101ec8b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +210 -132
app.py CHANGED
@@ -1,138 +1,216 @@
1
- """ Smart Dev Sandbox - تطبيق Gradio احترافي لتعديل ملفات ZIP / DOCX / PDF باستخدام نموذج AI ملف: app.py
2
-
3
- المتطلبات:
4
-
5
- gradio
6
-
7
- huggingface-hub
8
-
9
- pyzipper
10
-
11
- python-docx
12
-
13
- PyPDF2
14
-
15
-
16
- تثبيت سريع: python -m venv venv source venv/bin/activate # أو venv\Scripts\activate على Windows pip install gradio huggingface-hub pyzipper python-docx PyPDF2
17
-
18
- متغيرات بيئة مطلوبة:
19
-
20
- HF_TOKEN : مفتاح الوصول إلى Hugging Face Inference API """
21
-
22
-
23
- import os import re import shutil import tempfile import traceback from pathlib import Path from typing import Dict, Tuple, Optional, List
24
-
25
- import pyzipper import gradio as gr from huggingface_hub import InferenceClient from docx import Document from PyPDF2 import PdfReader
26
-
27
- إعدادات عامة
28
-
29
- ALLOWED_EXTENSIONS = {'.py', '.js', '.html', '.css', '.txt', '.json', '.md', '.php', '.yml', '.yaml', '.docx', '.pdf'} MAX_FILE_SIZE_BYTES = 200 * 1024 MAX_TOTAL_FILES = 500 MAX_EXTRACTED_BYTES = 20 * 1024 * 1024 MODEL_ID = os.environ.get('HF_MODEL_ID', 'moonshotai/Kimi-K2-Instruct') HF_TOKEN = os.environ.get('HF_TOKEN')
30
-
31
- if not HF_TOKEN: print("تحذير: لم يتم ضبط HF_TOKEN. تأكد من تعيين HF_TOKEN لتشغيل نموذج Hugging Face.")
32
-
33
- صناديق الرمل
34
-
35
- class Sandbox: def init(self): self.dir: Optional[str] = None self.files_content: Dict[str, str] = {} self.original_file_path: Optional[str] = None self.password: Optional[str] = None
36
-
37
- def cleanup(self):
38
- if self.dir and os.path.isdir(self.dir):
39
- shutil.rmtree(self.dir, ignore_errors=True)
40
- self.dir = None
41
- self.files_content = {}
42
- self.original_file_path = None
43
- self.password = None
44
-
45
- def reset(self, file_path: str, password: Optional[str] = None) -> Tuple[Optional[str], str]:
46
- self.cleanup()
47
- if not file_path:
48
- return None, "⚠️ يرجى رفع ملف ZIP / DOCX / PDF أولاً"
49
- self.dir = tempfile.mkdtemp(prefix='sandbox_')
50
- self.password = password
51
- self.original_file_path = file_path
52
- try:
53
- ext = Path(file_path).suffix.lower()
54
- if ext == '.zip':
55
- with pyzipper.AESZipFile(file_path, 'r') as zf:
56
- if password:
57
- zf.setpassword(password.encode('utf-8'))
58
- for info in zf.infolist():
59
- safe_name = os.path.normpath(info.filename).lstrip(os.sep)
60
- out_path = os.path.join(self.dir, safe_name)
61
- os.makedirs(os.path.dirname(out_path), exist_ok=True)
62
- zf.extract(info, path=self.dir)
63
- elif ext == '.docx':
64
- doc = Document(file_path)
65
- out_txt = os.path.join(self.dir, 'document.txt')
66
- with open(out_txt, 'w', encoding='utf-8') as f:
67
- for para in doc.paragraphs:
68
- f.write(para.text + '\n')
69
- elif ext == '.pdf':
70
- reader = PdfReader(file_path)
71
- out_txt = os.path.join(self.dir, 'document.txt')
72
- with open(out_txt, 'w', encoding='utf-8') as f:
73
- for page in reader.pages:
74
- f.write(page.extract_text() + '\n')
75
- else:
76
- return None, "❌ صيغة الملف غير مدعومة"
77
-
78
- # قراءة الملفات المسموح بها
79
  self.files_content = {}
80
- for path in Path(self.dir).rglob('*'):
81
- if path.is_file():
82
- self.files_content[str(path.relative_to(self.dir))] = path.read_text(encoding='utf-8', errors='ignore')
83
 
84
- return file_path, f"✅ تم استخراج {len(self.files_content)} ملف/ملفات قابلة للتعديل"
85
- except Exception as e:
 
 
 
86
  self.cleanup()
87
- return None, f"❌ استثناء: {e}"
88
-
89
- def get_context(self) -> str:
90
- return '\n'.join([f'--- FILE: {name} ---\n{content}' for name, content in self.files_content.items()])
91
-
92
- def update_file(self, name: str, content: str) -> None:
93
- if not self.dir:
94
- raise RuntimeError("لا توجد بيئة عمل (sandbox) نشطة")
95
- safe_name = os.path.normpath(name).lstrip(os.sep)
96
- out_path = os.path.join(self.dir, safe_name)
97
- os.makedirs(os.path.dirname(out_path), exist_ok=True)
98
- with open(out_path, 'w', encoding='utf-8') as f:
99
- f.write(content)
100
- self.files_content[safe_name] = content
101
-
102
- def create_zip(self) -> Tuple[Optional[str], str]:
103
- if not self.dir:
104
- return None, "❌ لا توجد ملفات لحزمها"
105
- out_fd, out_path = tempfile.mkstemp(prefix='modified_', suffix='.zip')
106
- os.close(out_fd)
107
- base_name = out_path[:-4]
108
- shutil.make_archive(base_name, 'zip', self.dir)
109
- return base_name + '.zip', "✅ تم إنشاء ZIP المعدل"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
  sandbox = Sandbox()
112
 
113
- واجهة Gradio
114
-
115
- with gr.Blocks(title="Smart Dev Sandbox — المبرمج الذكي PRO") as demo: gr.Markdown("# 🚀 المبرمج الذكي PRO — Sandbox احترافي لتعديل مشاريع") with gr.Row(): with gr.Column(scale=3): chatbot = gr.Chatbot(label="محادثة المساعد") msg = gr.Textbox(placeholder="اطلب تعديلًا", label="طلب التعديل") send_btn = gr.Button("إرسال") with gr.Column(scale=1): file_input = gr.File(label="اختر ملف المشروع (ZIP / DOCX / PDF)") pwd_input = gr.Textbox(label="كلمة مرور (اختياري)", type="password") analyze = gr.Button("🔍 تحليل الملفات") status = gr.Markdown("الحالة: جاهز") gr.HTML("<hr>") save_btn = gr.Button("✨ تطبيق التعديلات وإنشاء ZIP") download_output = gr.File(label="حمل الملف المعدل")
116
-
117
- def api_reset(file, password):
118
- if file is None:
119
- return None, "⚠️ ارفع ملف أولاً"
120
- return sandbox.reset(file.name, password)
121
-
122
- def api_send_message(user_message, chat_history):
123
- chat_history = chat_history or []
124
- chat_history.append(("user", user_message))
125
- sandbox_ctx = sandbox.get_context()
126
- full_response = f"نموذج AI سيجيب هنا على: {user_message}"
127
- chat_history.append(("assistant", full_response))
128
- return chat_history
129
-
130
- def api_apply_and_package(chat_history):
131
- out_zip, msg = sandbox.create_zip()
132
- return out_zip, msg
133
-
134
- analyze.click(api_reset, inputs=[file_input, pwd_input], outputs=[file_input, status])
135
- send_btn.click(api_send_message, inputs=[msg, chatbot], outputs=[chatbot])
136
- save_btn.click(api_apply_and_package, inputs=[chatbot], outputs=[download_output, status])
137
-
138
- if name == "main": demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ r"""
2
+ Smart Dev Sandbox - تطبيق بسيط وآمن يدعم ZIP / DOCX / PDF مع واجهة Gradio
3
+ متطلبات:
4
+ pip install gradio huggingface-hub pyzipper python-docx PyPDF2
5
+
6
+ متغيرات بيئة:
7
+ HF_TOKEN (اختياري، لتكامل Hugging Face)
8
+ HF_MODEL_ID (اختياري، معرف النموذج)
9
+ """
10
+ import os
11
+ import re
12
+ import shutil
13
+ import tempfile
14
+ import traceback
15
+ from pathlib import Path
16
+ from typing import Dict, Tuple, Optional, List
17
+
18
+ import pyzipper
19
+ import gradio as gr
20
+
21
+ # استيراد مكتبات لتحليل docx/pdf
22
+ try:
23
+ from docx import Document
24
+ except Exception:
25
+ Document = None
26
+
27
+ try:
28
+ from PyPDF2 import PdfReader
29
+ except Exception:
30
+ PdfReader = None
31
+
32
+ # إعدادات عامة
33
+ ALLOWED_EXTENSIONS = {
34
+ '.py', '.js', '.html', '.css', '.txt', '.json',
35
+ '.md', '.php', '.yml', '.yaml', '.docx', '.pdf'
36
+ }
37
+ MODEL_ID = os.environ.get('HF_MODEL_ID', 'moonshotai/Kimi-K2-Instruct')
38
+ HF_TOKEN = os.environ.get('HF_TOKEN')
39
+
40
+ if not HF_TOKEN:
41
+ print("ملاحظة: HF_TOKEN غير معطى — سيعمل التطبيق لكن بدون اتصال بنموذج Hugging Face.")
42
+
43
+ # ---------- Sandbox بسيط وآمن ----------
44
+ class Sandbox:
45
+ def __init__(self):
46
+ self.dir: Optional[str] = None
47
+ self.files_content: Dict[str, str] = {}
48
+ self.original_file_path: Optional[str] = None
49
+
50
+ def cleanup(self):
51
+ if self.dir and os.path.isdir(self.dir):
52
+ shutil.rmtree(self.dir, ignore_errors=True)
53
+ self.dir = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  self.files_content = {}
55
+ self.original_file_path = None
 
 
56
 
57
+ def reset(self, file_path: str, password: Optional[str] = None) -> Tuple[Optional[str], str]:
58
+ """
59
+ يدعم: .zip (استخراج)، .docx (استخراج نص)، .pdf (استخراج نص).
60
+ يعيد (path, message) أو (None, error_message).
61
+ """
62
  self.cleanup()
63
+ if not file_path:
64
+ return None, "⚠️ يرجى رفع ملف ZIP / DOCX / PDF أولاً."
65
+ self.dir = tempfile.mkdtemp(prefix='sandbox_')
66
+ self.original_file_path = file_path
67
+ try:
68
+ ext = Path(file_path).suffix.lower()
69
+ if ext == '.zip':
70
+ with pyzipper.AESZipFile(file_path, 'r') as zf:
71
+ # استخراج آمن: منع zip-slip
72
+ for info in zf.infolist():
73
+ name = info.filename
74
+ if name.startswith('__MACOSX') or name.strip() == '':
75
+ continue
76
+ # منع المسارات المطلقة أو ../../
77
+ safe_name = os.path.normpath(name).lstrip(os.sep)
78
+ dest_path = os.path.join(self.dir, safe_name)
79
+ os.makedirs(os.path.dirname(dest_path), exist_ok=True)
80
+ zf.extract(info, path=self.dir)
81
+ elif ext == '.docx':
82
+ if Document is None:
83
+ raise RuntimeError("مكتبة python-docx غير مثبتة")
84
+ doc = Document(file_path)
85
+ out_txt = os.path.join(self.dir, 'document.txt')
86
+ os.makedirs(os.path.dirname(out_txt), exist_ok=True)
87
+ with open(out_txt, 'w', encoding='utf-8') as f:
88
+ for para in doc.paragraphs:
89
+ f.write(para.text + '\n')
90
+ elif ext == '.pdf':
91
+ if PdfReader is None:
92
+ raise RuntimeError("مكتبة PyPDF2 غير مثبتة")
93
+ reader = PdfReader(file_path)
94
+ out_txt = os.path.join(self.dir, 'document.txt')
95
+ os.makedirs(os.path.dirname(out_txt), exist_ok=True)
96
+ with open(out_txt, 'w', encoding='utf-8') as f:
97
+ for page in reader.pages:
98
+ text = page.extract_text() or ''
99
+ f.write(text + '\n')
100
+ else:
101
+ self.cleanup()
102
+ return None, "❌ صيغة الملف غير مدعومة. ادعم: .zip, .docx, .pdf"
103
+
104
+ # جمع المحتويات للعرض / للسياق
105
+ self.files_content = {}
106
+ for p in Path(self.dir).rglob('*'):
107
+ if p.is_file():
108
+ rel = str(p.relative_to(self.dir))
109
+ try:
110
+ self.files_content[rel] = p.read_text(encoding='utf-8', errors='ignore')
111
+ except Exception:
112
+ self.files_content[rel] = "/* لا يمكن قراءة الملف */"
113
+
114
+ return file_path, f"✅ تم تجهيز {len(self.files_content)} ملف/ملفات داخل الـ sandbox."
115
+ except Exception as e:
116
+ self.cleanup()
117
+ return None, f"❌ استثناء أثناء المعالجة: {e}"
118
+
119
+ def get_context(self) -> str:
120
+ if not self.files_content:
121
+ return ""
122
+ return "\n".join(f"--- FILE: {name} ---\n{content}" for name, content in self.files_content.items())
123
+
124
+ def update_file(self, name: str, content: str) -> None:
125
+ if not self.dir:
126
+ raise RuntimeError("لا توجد بيئة عمل نشطة")
127
+ safe_name = os.path.normpath(name).lstrip(os.sep)
128
+ out_path = os.path.join(self.dir, safe_name)
129
+ os.makedirs(os.path.dirname(out_path), exist_ok=True)
130
+ with open(out_path, 'w', encoding='utf-8') as f:
131
+ f.write(content)
132
+ self.files_content[safe_name] = content
133
+
134
+ def create_zip(self) -> Tuple[Optional[str], str]:
135
+ if not self.dir:
136
+ return None, "❌ لا توجد ملفات لحزمها"
137
+ fd, temp_zip = tempfile.mkstemp(prefix='modified_', suffix='.zip')
138
+ os.close(fd)
139
+ base = temp_zip[:-4]
140
+ shutil.make_archive(base, 'zip', self.dir)
141
+ return base + '.zip', "✅ تم إنشاء ZIP المعدل"
142
 
143
  sandbox = Sandbox()
144
 
145
+ # ---------- واجهة Gradio ----------
146
+ with gr.Blocks(title="Smart Dev Sandbox") as demo:
147
+ gr.Markdown("# Smart Dev Sandbox — دعم ZIP / DOCX / PDF")
148
+ with gr.Row():
149
+ with gr.Column(scale=3):
150
+ chatbot = gr.Chatbot(label="محادثة")
151
+ msg = gr.Textbox(placeholder="اطرح سؤالاً أو اطلب تعديلًا...", label="الرسالة")
152
+ send_btn = gr.Button("إرسال")
153
+ with gr.Column(scale=1):
154
+ file_input = gr.File(label="اختر ملف (ZIP / DOCX / PDF)", file_types=[".zip", ".docx", ".pdf"])
155
+ pwd_input = gr.Textbox(label="كلمة مرور ZIP (اختياري)", type="password")
156
+ analyze_btn = gr.Button("🔍 تجهيز الملف")
157
+ status_md = gr.Markdown("**الحالة:** جاهز")
158
+ gr.HTML("<hr>")
159
+ save_btn = gr.Button("✨ تطبيق التعديلات وإنشاء ZIP")
160
+ download_file = gr.File(label="تحميل الناتج")
161
+
162
+ # دوال الواجهة
163
+ def api_reset(uploaded_file, password):
164
+ if uploaded_file is None:
165
+ return None, "⚠️ ارفع ملفًا أولًا"
166
+ # gradio يعطي كائن له .name أو .temp_path
167
+ file_path = getattr(uploaded_file, "name", None) or getattr(uploaded_file, "temp_path", None) or uploaded_file
168
+ return sandbox.reset(file_path, password)
169
+
170
+ def api_send_message(user_message, chat_history):
171
+ chat_history = chat_history or []
172
+ # يسمح بالدردشة حتى بدون ملف مرفوع
173
+ chat_history.append(("user", user_message))
174
+ if sandbox.files_content:
175
+ ctx = sandbox.get_context()
176
+ assistant_reply = f"تم استلام طلب تعديل. (سياق المشروع مرفوع — {len(sandbox.files_content)} ملف)." # placeholder
177
+ else:
178
+ assistant_reply = "يمكنني الإجابة على الأسئلة العامة الآن. لطلب تعديل ملفات، ارفع ZIP/DOCX/PDF ثم اضغط 'تجهيز الملف'."
179
+ chat_history.append(("assistant", assistant_reply))
180
+ return chat_history
181
+
182
+ def api_apply_and_package(chat_history):
183
+ # يأخذ آخر رد مساعد ويستخرج كتل التعديل بصيغة [ملف: path]```...```
184
+ if not sandbox.files_content:
185
+ return None, "⚠️ لا ملفات داخل الـ sandbox للتعديل."
186
+ if not chat_history:
187
+ return None, "⚠️ المحادثة فارغة."
188
+ # إيجاد آخر رد المساعد
189
+ last = None
190
+ for role, content in reversed(chat_history):
191
+ if role == 'assistant' and content and content.strip():
192
+ last = content
193
+ break
194
+ if not last:
195
+ return None, "⚠️ لا توجد تعديلات في المحادثة."
196
+ # استخراج كتل التعديل
197
+ pattern = re.compile(r"\[\s*(?:ملف|file)\s*:\s*([^\]]+?)\s*\]\s*```(?:[\\w\\-+]+)?\\n(.*?)\\n```", re.IGNORECASE | re.DOTALL)
198
+ matches = pattern.findall(last)
199
+ if not matches:
200
+ return None, "⚠️ لم يعثر على كتل تعديل بصيغة [ملف: ...]```...```"
201
+ applied = 0
202
+ for name, code in matches:
203
+ try:
204
+ sandbox.update_file(name.strip(), code)
205
+ applied += 1
206
+ except Exception as e:
207
+ print(f"فشل تطبيق {name}: {e}")
208
+ out_zip, msg = sandbox.create_zip()
209
+ return out_zip, f"✅ تم تطبيق {applied} ملفات. {msg}"
210
+
211
+ analyze_btn.click(api_reset, inputs=[file_input, pwd_input], outputs=[file_input, status_md])
212
+ send_btn.click(api_send_message, inputs=[msg, chatbot], outputs=[chatbot])
213
+ save_btn.click(api_apply_and_package, inputs=[chatbot], outputs=[download_file, status_md])
214
+
215
+ if __name__ == "__main__":
216
+ demo.launch()