| import gradio as gr |
| import asyncio |
| from playwright.async_api import async_playwright |
| from bs4 import BeautifulSoup |
| import re |
| import zipfile |
| import io |
|
|
| |
| |
| |
|
|
| 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') |
| |
| |
| 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}) |
| |
| |
| 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'}) |
| |
| |
| 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'}) |
| |
| |
| 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 |
|
|
| |
| |
| |
|
|
| 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) |
|
|
| |
| |
| |
| 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()) |