File size: 8,397 Bytes
3ad0c22
c44cded
 
 
3ad0c22
 
 
 
 
5497102
3ad0c22
 
c44cded
3ad0c22
c44cded
 
 
 
5497102
c44cded
 
 
 
 
 
5497102
c44cded
 
 
 
 
5497102
 
 
 
 
c44cded
5497102
 
 
 
 
 
 
 
 
c44cded
5497102
 
c44cded
5497102
c44cded
 
a9fba96
c44cded
 
 
3ad0c22
 
c44cded
5497102
 
 
 
 
 
 
 
 
 
 
 
 
c44cded
 
3ad0c22
c44cded
3ad0c22
c44cded
5497102
3ad0c22
 
 
 
67b4f79
3ad0c22
 
a9fba96
5497102
423fde0
5497102
3ad0c22
5497102
 
 
423fde0
 
3ad0c22
 
5497102
423fde0
67b4f79
 
423fde0
 
 
5497102
 
423fde0
5497102
 
423fde0
5497102
423fde0
3ad0c22
423fde0
67b4f79
423fde0
 
c44cded
67b4f79
 
5497102
423fde0
5497102
67b4f79
5497102
423fde0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5497102
67b4f79
c44cded
423fde0
 
 
 
 
 
67b4f79
423fde0
 
 
 
 
 
c44cded
423fde0
 
c44cded
423fde0
67b4f79
 
423fde0
c44cded
423fde0
3ad0c22
c44cded
 
423fde0
c44cded
 
423fde0
 
 
 
3ad0c22
 
 
c44cded
423fde0
5497102
3ad0c22
 
 
a9fba96
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
import gradio as gr
import asyncio
from playwright.async_api import async_playwright
from bs4 import BeautifulSoup
import re
import zipfile
import io

# ==========================================
# 🔧 Backend Logic
# ==========================================

async def scan_page_for_components(url):
    components = []
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        context = await browser.new_context(
            viewport={'width': 1920, 'height': 1080},
            user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
        )
        page = await context.new_page()
        
        try:
            await page.goto(url, wait_until="networkidle", timeout=30000)
            await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
            await page.wait_for_timeout(800)
            await page.evaluate("window.scrollTo(0, 0)")
            
            html_content = await page.content()
            soup = BeautifulSoup(html_content, 'html.parser')
            
            # 1. Buttons
            for btn in soup.find_all(['button', 'a'], class_=re.compile(r'btn|button', re.I)):
                txt = btn.get_text(strip=True)
                if txt and len(txt) < 40:
                    components.append({'type': '🔘 Button', 'html': str(btn), 'classes': btn.get('class', []), 'preview': txt})
            
            # 2. Cards
            for card in soup.find_all(class_=re.compile(r'card|product|item|post|entry', re.I)):
                if card.find('img') or card.find(['h1','h2','h3','h4']):
                    if not any(c.get('class') and 'card' in str(c['class']).lower() for c in card.parents):
                        components.append({'type': '🃏 Card', 'html': str(card), 'classes': card.get('class', []), 'preview': 'Card'})
                        
            # 3. Navbars
            for nav in soup.find_all(['nav', 'header'], class_=re.compile(r'nav|header|menu', re.I)):
                components.append({'type': '🧭 Navbar', 'html': str(nav), 'classes': nav.get('class', []), 'preview': 'Navigation'})
                
            # 4. Hero Sections
            for sec in soup.find_all('section'):
                if sec.get('class') and any('hero' in str(c).lower() for c in sec.get('class')):
                    components.append({'type': '🎨 Hero', 'html': str(sec), 'classes': sec.get('class', []), 'preview': 'Hero Section'})

        except Exception as e:
            components.append({'type': '⚠️ Error', 'html': f'<p style="color:red;padding:20px">{str(e)}</p>', 'classes': [], 'preview': 'Failed'})
        finally:
            await browser.close()
            
    return components

def generate_react_code(html_str, classes):
    clean = html_str.replace('class=', 'className=').replace('for=', 'htmlFor=')
    classes_comment = ' '.join(classes)
    return (
        f"import React from 'react';\n\n"
        f"export default function ExtractedComponent() {{\n"
        f"  return (\n"
        f"    <>\n"
        f"      {{/* Detected classes: {classes_comment} */}}\n"
        f"      {clean}\n"
        f"    </>\n"
        f"  );\n"
        f"}}"
    )

def create_zip_file(components):
    zip_buffer = io.BytesIO()
    with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
        for i, comp in enumerate(components):
            zip_file.writestr(f"component_{i+1}.html", comp['html'])
            zip_file.writestr(f"component_{i+1}.jsx", generate_react_code(comp['html'], comp['classes']))
    zip_buffer.seek(0)
    return zip_buffer

# ==========================================
# 🎨 Gradio UI
# ==========================================

with gr.Blocks(title="UI Extractor Pro") as demo:
    gr.Markdown("# 🛠️ UI Component Extractor")
    gr.Markdown("أدخل رابط موقع لاستخراج المكونات، معاينتها، وتحميل كود React/HTML جاهز.")

    with gr.Row():
        url_input = gr.Textbox(label="🔗 Website URL", placeholder="https://example.com", scale=3)
        extract_btn = gr.Button("🔍 Extract Components", variant="primary", scale=1)

    status_box = gr.Textbox(label="Status", interactive=False, value="Ready")
    components_state = gr.State([])
    
    with gr.Row():
        with gr.Column(scale=1):
            gr.Markdown("### 📦 Detected Components")
            preview_selector = gr.Radio(label="Click to Preview", choices=["No components yet"], interactive=True)
            download_selector = gr.CheckboxGroup(label="Select for ZIP Download", choices=["No components yet"], interactive=True)
            
            download_btn = gr.Button("📥 Download Selected as ZIP", variant="secondary")
            output_file = gr.File(label="Download", interactive=False)
            
        with gr.Column(scale=2):
            gr.Markdown("### 👁️ Live Preview & Code")
            with gr.Tabs():
                with gr.Tab("Live Preview"):
                    preview_html = gr.HTML(label="Component Preview", elem_id="preview-box")
                with gr.Tab("React Code"):
                    code_display = gr.Code(label="React Component", language="javascript", lines=20)

    # ==========================================
    # 🔄 Event Handlers (Fixed: No yield, only return)
    # ==========================================
    async def on_extract(url):
        if not url.startswith('http'):
            return "❌ الرجاء إدخال رابط صحيح", [], gr.Radio(choices=["Invalid URL"]), gr.CheckboxGroup(choices=["Invalid URL"]), "", "", None

        try:
            results = await scan_page_for_components(url)
            if not results:
                return "⚠️ لم يتم العثور على مكونات.", [], gr.Radio(choices=["No results"]), gr.CheckboxGroup(choices=["No results"]), "", "", None
                
            choices = [f"{c['type']} - {c['preview']}" for c in results]
            first = results[0]
            
            safe_preview = f"""
            <div style="border:1px solid #e2e8f0; padding:20px; border-radius:8px; background:#ffffff; overflow:auto; max-height:500px;">
                {first['html']}
            </div>
            """
            return (
                f"✅ تم العثور على {len(results)} مكونات.",
                results,
                gr.Radio(choices=choices, value=choices[0]),
                gr.CheckboxGroup(choices=choices),
                safe_preview,
                generate_react_code(first['html'], first['classes']),
                None
            )
        except Exception as e:
            return f"❌ خطأ: {str(e)}", [], gr.Radio(choices=["Error"]), gr.CheckboxGroup(choices=["Error"]), "", "", None

    async def on_preview_select(selected_name, components):
        if not selected_name or not components:
            return "", ""
        comp = next((c for c in components if f"{c['type']} - {c['preview']}" == selected_name), None)
        if not comp:
            return "", ""
            
        safe_preview = f"""
        <div style="border:1px solid #e2e8f0; padding:20px; border-radius:8px; background:#ffffff; overflow:auto; max-height:500px;">
            {comp['html']}
        </div>
        """
        return safe_preview, generate_react_code(comp['html'], comp['classes'])

    async def on_download(selected_names, components):
        if not selected_names or not components:
            return None
        selected_comps = [c for c in components if f"{c['type']} - {c['preview']}" in selected_names]
        if not selected_comps:
            return None
        return create_zip_file(selected_comps)

    # ربط الأحداث
    extract_btn.click(
        fn=on_extract,
        inputs=url_input,
        outputs=[status_box, components_state, preview_selector, download_selector, preview_html, code_display, output_file]
    )
    
    preview_selector.change(
        fn=on_preview_select,
        inputs=[preview_selector, components_state],
        outputs=[preview_html, code_display]
    )
    
    download_btn.click(
        fn=on_download,
        inputs=[download_selector, components_state],
        outputs=output_file
    )

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860, theme=gr.themes.Soft())