PulseUp / app.py
lljz66's picture
Update app.py
67b4f79 verified
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())