File size: 13,921 Bytes
74cee4d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# פרויקט Python מלא: מחולל אתרי אינטרנט AI אוטומטי להרצה ב-Hugging Face Spaces (Gradio)
# גרסה: 1.0
# תואם בדיוק לדרישות: מודל AI, Gradio, עורך קוד, תצוגה מקדימה, ניהול פרויקטים, מספר דפים, יצוא ZIP
# הסבר: הקוד משתמש ב-InferenceClient (huggingface_hub) למודל Qwen2.5-Coder-1.5B-Instruct – קל ומהיר ב-Spaces ללא צורך ב-GPU כבד.
# כל הקבצים נשמרים בזיכרון (State), תומך בעריכה, תצוגה מקדימה עם inline CSS/JS, ושמירה/טעינה כ-JSON + ZIP.
# הערות מפורטות בכל חלק – כפי שביקשת.

import gradio as gr
from huggingface_hub import InferenceClient
import json
import zipfile
import io
import re

# ====================== 1. מודל AI חזק (Code Generation) ======================
# מודל Hugging Face מתאים במיוחד ליצירת HTML/CSS/JS איכותי ומוכן להרצה
client = InferenceClient(model="Qwen/Qwen2.5-Coder-1.5B-Instruct")

def generate_website(description: str, primary_color: str, design_style: str, page_names_str: str):
    """
    יוצר את כל הקבצים באמצעות מודל AI.
    הפרומפט באנגלית (מודלים לקוד טובים יותר באנגלית) אבל התוצאה מותאמת לעברית/עיצוב.
    """
    page_names = [p.strip() for p in page_names_str.split(",") if p.strip()]
    if not page_names:
        page_names = ["index"]
    
    prompt = f"""You are an expert full-stack web developer. Create a complete, beautiful, responsive multi-page website.

Description: {description}
Primary color: {primary_color}
Design style: {design_style} (use Tailwind CSS via CDN + custom CSS)
Pages: {', '.join(page_names)}

Requirements:
- Use HTML5, Tailwind CSS CDN, modern design.
- Each HTML file must contain: <link rel="stylesheet" href="style.css"> and <script src="script.js"></script>
- Add nice navigation between pages.
- Include some JavaScript interactivity (buttons, smooth scroll, mobile menu).
- Make it production-ready and beautiful.

Return ONLY a valid JSON object (no extra text):
{{
  "files": {{
    "style.css": "FULL CSS CODE HERE",
    "script.js": "FULL JS CODE HERE",
    "index.html": "FULL HTML CODE HERE",
    "about.html": "FULL HTML CODE HERE",
    ... (one file for each page)
  }}
}}
"""

    try:
        response = client.text_generation(
            prompt=prompt,
            max_new_tokens=8192,
            temperature=0.6,
            top_p=0.9,
            do_sample=True
        )
        
        # ניקוי אם המודל הוסיף markdown
        response = response.strip()
        if response.startswith("```json"):
            response = response[7:]
        if response.endswith("```"):
            response = response[:-3]
        
        data = json.loads(response)
        files = data.get("files", {})
        
        # בדיקת בטיחות – תמיד קיימים קבצים בסיסיים
        if "style.css" not in files:
            files["style.css"] = f"body {{ color: {primary_color}; font-family: system-ui; }}"
        if "script.js" not in files:
            files["script.js"] = "// JS interactivity\nconsole.log('Site loaded!');"
        
        return files, "✅ האתר נוצר בהצלחה! ערוך, צפה והורד."
    except Exception as e:
        return {}, f"❌ שגיאה: {str(e)}\nנסה תיאור יותר מפורט או לחץ שוב."

# ====================== 2. הכנת תצוגה מקדימה (iframe-like עם inline) ======================
def prepare_preview_html(files: dict, page_name: str):
    """הופך את הדף ל-single HTML עם CSS + JS inline כדי שיעבוד בתוך Gradio."""
    if not files or page_name not in files:
        return "<h1 style='text-align:center;padding:50px;'>בחר דף להצגה</h1>"
    
    html = files[page_name]
    css = files.get("style.css", "")
    js = files.get("script.js", "")
    
    # הסרת קישורים חיצוניים (כדי שהכל יהיה inline)
    html = re.sub(r'<link[^>]*href=["\']style\.css["\'][^>]*>', '', html, flags=re.IGNORECASE)
    html = re.sub(r'<script[^>]*src=["\']script\.js["\'][^>]*></script>', '', html, flags=re.IGNORECASE)
    
    # הוספת inline CSS
    if re.search(r'</head>', html, re.IGNORECASE):
        html = re.sub(r'(?i)</head>', f'<style>\n{css}\n</style></head>', html)
    else:
        html = f'<head><style>\n{css}\n</style></head>\n{html}'
    
    # הוספת inline JS
    if re.search(r'</body>', html, re.IGNORECASE):
        html = re.sub(r'(?i)</body>', f'<script>\n{js}\n</script></body>', html)
    else:
        html += f'\n<script>\n{js}\n</script>'
    
    return html

# ====================== 3. יצוא ZIP ======================
def create_zip_download(files: dict):
    """יוצר ZIP בזיכרון עם כל הקבצים הנפרדים."""
    if not files:
        return None
    buffer = io.BytesIO()
    with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as zf:
        for filename, content in files.items():
            zf.writestr(filename, content.encode("utf-8"))
    buffer.seek(0)
    return buffer.getvalue()

# ====================== 4. שמירה וטעינה של פרויקט (JSON) ======================
def save_project_json(files: dict):
    if not files:
        return None
    return json.dumps(files, ensure_ascii=False, indent=2).encode("utf-8")

def load_project(uploaded_file):
    """טוען פרויקט JSON שהמשתמש העלה."""
    if uploaded_file is None:
        return {}, "לא נבחר קובץ"
    try:
        content = uploaded_file.decode("utf-8")
        files = json.loads(content)
        return files, "✅ פרויקט נטען בהצלחה!"
    except Exception as e:
        return {}, f"❌ שגיאה בטעינה: {str(e)}"

# ====================== 5. פונקציות עזר לעורך ======================
def update_editor(selected_file, current_files):
    """מעדכן את עורך הקוד לפי הקובץ שנבחר."""
    if not current_files or selected_file not in current_files:
        return "// אין קובץ", "javascript"
    ext = selected_file.split(".")[-1].lower()
    lang = "html" if ext == "html" else "css" if ext == "css" else "javascript"
    return current_files[selected_file], lang

def save_edited_code(selected_file, new_code, current_files):
    """שומר שינויים מהעורך."""
    if selected_file and current_files:
        current_files[selected_file] = new_code
        return current_files, "✅ שינויים נשמרו!", create_zip_download(current_files), save_project_json(current_files)
    return current_files, "❌ לא נשמר", None, None

# ====================== 6. ממשק Gradio מלא ======================
with gr.Blocks(title="מחולל אתרי AI – Gradio", theme=gr.themes.Soft()) as iface:
    gr.Markdown("# 🛠️ מחולל אתרי אינטרנט אוטומטי עם AI\n"
                "תאר → בחר צבע וסגנון → יצור → ערוך → צפה → הורד ZIP\n"
                "תומך ב-Landing Page, Blog, Portfolio, Dashboard ועוד!")
    
    files_state = gr.State({})  # שומר את כל הקבצים בזיכרון
    
    with gr.Tab("✨ יצירת אתר חדש"):
        with gr.Row():
            with gr.Column(scale=2):
                description_input = gr.Textbox(
                    label="📝 תיאור האתר",
                    placeholder="אתר Landing Page לחנות בגדים אופנתית עם דף מוצרים, בלוג וטופס יצירת קשר...",
                    lines=4
                )
                primary_color_input = gr.ColorPicker(label="🎨 צבע עיקרי", value="#2563eb")
                design_style_input = gr.Dropdown(
                    choices=["מודרני", "מינימליסטי", "כהה", "תוסס", "תאגידי", "פורטפוליו"],
                    label="🎨 סגנון עיצוב",
                    value="מודרני"
                )
                page_names_input = gr.Textbox(
                    label="📄 שמות הדפים (מופרדים בפסיקים)",
                    value="index,about,services,contact",
                    placeholder="index,blog,portfolio"
                )
                generate_button = gr.Button("🚀 יצור אתר!", variant="primary", size="large")
            
            with gr.Column(scale=1):
                status_output = gr.Textbox(label="סטטוס", interactive=False, value="מוכן ליצירה...")
        
        gr.Markdown("### 👀 תצוגה מקדימה + עורך קוד")
        with gr.Row():
            with gr.Column(scale=1):
                preview_page_dropdown = gr.Dropdown(label="בחר דף להצגה", choices=[], interactive=True)
                live_preview = gr.HTML(
                    value="<div style='text-align:center;padding:80px;background:#f8fafc;border-radius:12px;'>התצוגה המקדימה תופיע כאן</div>",
                    label="תצוגה מקדימה (מתעדכנת אוטומטית)"
                )
                refresh_preview_btn = gr.Button("🔄 רענן תצוגה")
            
            with gr.Column(scale=1):
                file_dropdown = gr.Dropdown(label="בחר קובץ לעריכה", choices=[], interactive=True)
                code_editor = gr.Code(
                    label="עורך קוד (HTML / CSS / JS)",
                    language="html",
                    lines=22,
                    interactive=True
                )
                save_edit_button = gr.Button("💾 שמור שינויים בקוד")
    
    with gr.Tab("📁 ניהול פרויקטים & יצוא"):
        gr.Markdown("### שמור / טען פרויקטים")
        with gr.Row():
            upload_project = gr.File(label="העלה פרויקט JSON קודם", file_types=[".json"], type="binary")
            load_status = gr.Textbox(label="סטטוס טעינה", interactive=False)
        
        with gr.Row():
            save_json_button = gr.DownloadButton(label="💾 שמור פרויקט כ-JSON", value=None, visible=False)
            download_zip_button = gr.DownloadButton(label="📦 הורד אתר מלא כ-ZIP", value=None, visible=False)
    
    # ====================== אירועים (Events) ======================
    generate_button.click(
        fn=lambda desc, color, style, pages, state: (
            *generate_website(desc, color, style, pages),
            # outputs: files, status, file_dropdown, preview_page_dropdown, live_preview, zip_bytes, json_bytes
        ),
        inputs=[description_input, primary_color_input, design_style_input, page_names_input, files_state],
        outputs=[
            files_state,
            status_output,
            file_dropdown,
            preview_page_dropdown,
            live_preview,
            download_zip_button,
            save_json_button
        ]
    )
    
    # עדכון עורך קוד
    file_dropdown.change(
        fn=update_editor,
        inputs=[file_dropdown, files_state],
        outputs=[code_editor]
    )
    
    # שמירת עריכה + עדכון כפתורי הורדה
    save_edit_button.click(
        fn=save_edited_code,
        inputs=[file_dropdown, code_editor, files_state],
        outputs=[files_state, status_output, download_zip_button, save_json_button]
    )
    
    # רענון תצוגה מקדימה
    def refresh_preview(page, state):
        return prepare_preview_html(state, page)
    
    preview_page_dropdown.change(
        fn=refresh_preview,
        inputs=[preview_page_dropdown, files_state],
        outputs=[live_preview]
    )
    refresh_preview_btn.click(
        fn=refresh_preview,
        inputs=[preview_page_dropdown, files_state],
        outputs=[live_preview]
    )
    
    # טעינת פרויקט JSON
    def handle_upload(uploaded, state):
        new_files, msg = load_project(uploaded)
        if new_files:
            html_pages = [f for f in new_files if f.endswith(".html")]
            all_files = list(new_files.keys())
            return (
                new_files,
                msg,
                gr.Dropdown(choices=all_files, value=all_files[0] if all_files else None),
                gr.Dropdown(choices=html_pages, value=html_pages[0] if html_pages else None),
                prepare_preview_html(new_files, html_pages[0] if html_pages else ""),
                create_zip_download(new_files),
                save_project_json(new_files)
            )
        return state, msg, gr.Dropdown(), gr.Dropdown(), live_preview.value, None, None
    
    upload_project.upload(
        fn=handle_upload,
        inputs=[upload_project, files_state],
        outputs=[
            files_state,
            load_status,
            file_dropdown,
            preview_page_dropdown,
            live_preview,
            download_zip_button,
            save_json_button
        ]
    )
    
    gr.Markdown("---\n"
                "**טיפים:**\n"
                "• התצוגה המקדימה מראה דף אחד בכל פעם (קישורים לא עובדים כי אין שרת). הורד ZIP כדי לראות את כל האתר.\n"
                "• אחרי עריכה – לחץ \"שמור שינויים\" ואז \"רענן תצוגה\".\n"
                "• כל הפרויקטים נשמרים רק בדפדפן – הורד JSON כדי לשמור.")

# ====================== 7. הרצה ======================
if __name__ == "__main__":
    iface.launch(
        server_name="0.0.0.0",
        server_port=7860,
        share=False,          # ב-Spaces זה אוטומטי
        debug=True
    )