riazmo commited on
Commit
17a7a81
·
verified ·
1 Parent(s): ba25cd5

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -395
app.py DELETED
@@ -1,395 +0,0 @@
1
- """
2
- Design System Extractor v2 — Main Application
3
- ==============================================
4
-
5
- A semi-automated, human-in-the-loop agentic system that reverse-engineers
6
- design systems from live websites.
7
-
8
- Usage:
9
- python app.py
10
-
11
- Or for development:
12
- gradio app.py
13
- """
14
-
15
- import asyncio
16
- import gradio as gr
17
- from datetime import datetime
18
-
19
- from agents.graph import WorkflowRunner, create_workflow
20
- from agents.crawler import PageDiscoverer, discover_pages
21
- from agents.extractor import TokenExtractor
22
- from core.token_schema import Viewport, DiscoveredPage
23
- from config.settings import get_settings, is_debug
24
-
25
-
26
- # =============================================================================
27
- # GLOBAL STATE
28
- # =============================================================================
29
-
30
- workflow_runner: WorkflowRunner | None = None
31
- current_extraction: dict = {}
32
-
33
-
34
- # =============================================================================
35
- # STAGE 1: URL INPUT & PAGE DISCOVERY
36
- # =============================================================================
37
-
38
- async def discover_site_pages(url: str, progress=gr.Progress()) -> tuple:
39
- """
40
- Discover pages from a website URL.
41
-
42
- Returns tuple of (status_message, pages_dataframe, pages_json)
43
- """
44
- if not url or not url.startswith(("http://", "https://")):
45
- return "Please enter a valid URL starting with http:// or https://", None, None
46
-
47
- progress(0, desc="Initializing browser...")
48
-
49
- try:
50
- discoverer = PageDiscoverer()
51
-
52
- def update_progress(p):
53
- progress(p, desc=f"Discovering pages... ({int(p*100)}%)")
54
-
55
- pages = await discoverer.discover(url, progress_callback=update_progress)
56
-
57
- # Format for display
58
- pages_data = []
59
- for page in pages:
60
- pages_data.append({
61
- "Select": page.selected,
62
- "URL": page.url,
63
- "Title": page.title or "(No title)",
64
- "Type": page.page_type.value,
65
- "Status": "✓" if not page.error else f"⚠ {page.error}",
66
- })
67
-
68
- # Store for later use
69
- current_extraction["discovered_pages"] = pages
70
- current_extraction["base_url"] = url
71
-
72
- status = f"✓ Found {len(pages)} pages. Select the pages you want to extract tokens from."
73
-
74
- return status, pages_data, [p.model_dump() for p in pages]
75
-
76
- except Exception as e:
77
- return f"❌ Error: {str(e)}", None, None
78
-
79
-
80
- async def start_extraction(pages_selection: list, viewport_choice: str, progress=gr.Progress()) -> tuple:
81
- """
82
- Start token extraction from selected pages.
83
-
84
- Returns tuple of (status, colors_data, typography_data, spacing_data)
85
- """
86
- if not pages_selection:
87
- return "Please select at least one page", None, None, None
88
-
89
- # Get selected URLs
90
- selected_urls = []
91
- for row in pages_selection:
92
- if row.get("Select", False):
93
- selected_urls.append(row["URL"])
94
-
95
- if not selected_urls:
96
- return "Please select at least one page using the checkboxes", None, None, None
97
-
98
- # Determine viewport
99
- viewport = Viewport.DESKTOP if viewport_choice == "Desktop (1440px)" else Viewport.MOBILE
100
-
101
- progress(0, desc=f"Starting {viewport.value} extraction...")
102
-
103
- try:
104
- extractor = TokenExtractor(viewport=viewport)
105
-
106
- def update_progress(p):
107
- progress(p, desc=f"Extracting tokens... ({int(p*100)}%)")
108
-
109
- result = await extractor.extract(selected_urls, progress_callback=update_progress)
110
-
111
- # Store result
112
- current_extraction[f"{viewport.value}_tokens"] = result
113
-
114
- # Format colors for display
115
- colors_data = []
116
- for color in sorted(result.colors, key=lambda c: -c.frequency)[:50]:
117
- colors_data.append({
118
- "Accept": True,
119
- "Color": color.value,
120
- "Frequency": color.frequency,
121
- "Context": ", ".join(color.contexts[:3]),
122
- "Contrast (White)": f"{color.contrast_white}:1",
123
- "AA Text": "✓" if color.wcag_aa_small_text else "✗",
124
- "Confidence": color.confidence.value,
125
- })
126
-
127
- # Format typography for display
128
- typography_data = []
129
- for typo in sorted(result.typography, key=lambda t: -t.frequency)[:30]:
130
- typography_data.append({
131
- "Accept": True,
132
- "Font": typo.font_family,
133
- "Size": typo.font_size,
134
- "Weight": typo.font_weight,
135
- "Line Height": typo.line_height,
136
- "Elements": ", ".join(typo.elements[:3]),
137
- "Frequency": typo.frequency,
138
- })
139
-
140
- # Format spacing for display
141
- spacing_data = []
142
- for space in sorted(result.spacing, key=lambda s: s.value_px)[:20]:
143
- spacing_data.append({
144
- "Accept": True,
145
- "Value": space.value,
146
- "Frequency": space.frequency,
147
- "Context": ", ".join(space.contexts[:2]),
148
- "Fits 8px": "✓" if space.fits_base_8 else "",
149
- "Outlier": "⚠" if space.is_outlier else "",
150
- })
151
-
152
- # Summary
153
- status = f"""✓ Extraction Complete ({viewport.value})
154
-
155
- **Summary:**
156
- - Pages crawled: {len(result.pages_crawled)}
157
- - Colors found: {len(result.colors)}
158
- - Typography styles: {len(result.typography)}
159
- - Spacing values: {len(result.spacing)}
160
- - Font families: {len(result.font_families)}
161
- - Detected spacing base: {result.spacing_base or 'Unknown'}px
162
- - Duration: {result.extraction_duration_ms}ms
163
-
164
- {f'⚠ Warnings: {len(result.warnings)}' if result.warnings else ''}
165
- {f'❌ Errors: {len(result.errors)}' if result.errors else ''}
166
- """
167
-
168
- return status, colors_data, typography_data, spacing_data
169
-
170
- except Exception as e:
171
- return f"❌ Extraction failed: {str(e)}", None, None, None
172
-
173
-
174
- def export_tokens_json():
175
- """Export current tokens to JSON."""
176
- import json
177
-
178
- result = {}
179
-
180
- if "desktop_tokens" in current_extraction:
181
- desktop = current_extraction["desktop_tokens"]
182
- result["desktop"] = {
183
- "colors": [c.model_dump() for c in desktop.colors],
184
- "typography": [t.model_dump() for t in desktop.typography],
185
- "spacing": [s.model_dump() for s in desktop.spacing],
186
- "metadata": desktop.summary(),
187
- }
188
-
189
- if "mobile_tokens" in current_extraction:
190
- mobile = current_extraction["mobile_tokens"]
191
- result["mobile"] = {
192
- "colors": [c.model_dump() for c in mobile.colors],
193
- "typography": [t.model_dump() for t in mobile.typography],
194
- "spacing": [s.model_dump() for s in mobile.spacing],
195
- "metadata": mobile.summary(),
196
- }
197
-
198
- return json.dumps(result, indent=2, default=str)
199
-
200
-
201
- # =============================================================================
202
- # UI BUILDING
203
- # =============================================================================
204
-
205
- def create_ui():
206
- """Create the Gradio interface."""
207
-
208
- settings = get_settings()
209
-
210
- with gr.Blocks(
211
- title="Design System Extractor v2",
212
- theme=gr.themes.Soft(),
213
- css="""
214
- .token-preview { padding: 10px; border-radius: 8px; }
215
- .color-swatch { width: 40px; height: 40px; border-radius: 4px; display: inline-block; }
216
- """
217
- ) as app:
218
-
219
- # Header
220
- gr.Markdown("""
221
- # 🎨 Design System Extractor v2
222
-
223
- **Reverse-engineer design systems from live websites.**
224
-
225
- This tool crawls your website, extracts design tokens (colors, typography, spacing),
226
- and helps you rebuild a structured design system.
227
-
228
- ---
229
- """)
230
-
231
- # =================================================================
232
- # STAGE 1: URL Input & Discovery
233
- # =================================================================
234
-
235
- with gr.Accordion("📍 Stage 1: Website Discovery", open=True):
236
-
237
- gr.Markdown("""
238
- **Step 1:** Enter your website URL and discover pages.
239
- The system will automatically find and classify pages for extraction.
240
- """)
241
-
242
- with gr.Row():
243
- url_input = gr.Textbox(
244
- label="Website URL",
245
- placeholder="https://example.com",
246
- scale=4,
247
- )
248
- discover_btn = gr.Button("🔍 Discover Pages", variant="primary", scale=1)
249
-
250
- discovery_status = gr.Markdown("")
251
-
252
- pages_table = gr.Dataframe(
253
- headers=["Select", "URL", "Title", "Type", "Status"],
254
- datatype=["bool", "str", "str", "str", "str"],
255
- interactive=True,
256
- label="Discovered Pages",
257
- visible=False,
258
- )
259
-
260
- pages_json = gr.JSON(visible=False) # Hidden storage
261
-
262
- # =================================================================
263
- # STAGE 2: Extraction
264
- # =================================================================
265
-
266
- with gr.Accordion("🔬 Stage 2: Token Extraction", open=False) as extraction_accordion:
267
-
268
- gr.Markdown("""
269
- **Step 2:** Select pages and viewport, then extract design tokens.
270
- """)
271
-
272
- with gr.Row():
273
- viewport_radio = gr.Radio(
274
- choices=["Desktop (1440px)", "Mobile (375px)"],
275
- value="Desktop (1440px)",
276
- label="Viewport",
277
- )
278
- extract_btn = gr.Button("🚀 Extract Tokens", variant="primary")
279
-
280
- extraction_status = gr.Markdown("")
281
-
282
- with gr.Tabs():
283
- with gr.Tab("🎨 Colors"):
284
- colors_table = gr.Dataframe(
285
- headers=["Accept", "Color", "Frequency", "Context", "Contrast (White)", "AA Text", "Confidence"],
286
- datatype=["bool", "str", "number", "str", "str", "str", "str"],
287
- interactive=True,
288
- label="Extracted Colors",
289
- )
290
-
291
- with gr.Tab("📝 Typography"):
292
- typography_table = gr.Dataframe(
293
- headers=["Accept", "Font", "Size", "Weight", "Line Height", "Elements", "Frequency"],
294
- datatype=["bool", "str", "str", "number", "str", "str", "number"],
295
- interactive=True,
296
- label="Extracted Typography",
297
- )
298
-
299
- with gr.Tab("📏 Spacing"):
300
- spacing_table = gr.Dataframe(
301
- headers=["Accept", "Value", "Frequency", "Context", "Fits 8px", "Outlier"],
302
- datatype=["bool", "str", "number", "str", "str", "str"],
303
- interactive=True,
304
- label="Extracted Spacing",
305
- )
306
-
307
- # =================================================================
308
- # STAGE 3: Export
309
- # =================================================================
310
-
311
- with gr.Accordion("📦 Stage 3: Export", open=False):
312
-
313
- gr.Markdown("""
314
- **Step 3:** Review and export your design tokens.
315
- """)
316
-
317
- with gr.Row():
318
- export_btn = gr.Button("📥 Export JSON", variant="secondary")
319
-
320
- export_output = gr.Code(
321
- label="Exported Tokens (JSON)",
322
- language="json",
323
- lines=20,
324
- )
325
-
326
- # =================================================================
327
- # EVENT HANDLERS
328
- # =================================================================
329
-
330
- # Discovery
331
- discover_btn.click(
332
- fn=discover_site_pages,
333
- inputs=[url_input],
334
- outputs=[discovery_status, pages_table, pages_json],
335
- ).then(
336
- fn=lambda: gr.update(visible=True),
337
- outputs=[pages_table],
338
- )
339
-
340
- # Extraction
341
- extract_btn.click(
342
- fn=start_extraction,
343
- inputs=[pages_table, viewport_radio],
344
- outputs=[extraction_status, colors_table, typography_table, spacing_table],
345
- )
346
-
347
- # Export
348
- export_btn.click(
349
- fn=export_tokens_json,
350
- outputs=[export_output],
351
- )
352
-
353
- # =================================================================
354
- # FOOTER
355
- # =================================================================
356
-
357
- gr.Markdown("""
358
- ---
359
-
360
- **Design System Extractor v2** | Built with LangGraph + Gradio
361
-
362
- *A semi-automated co-pilot for design system recovery and modernization.*
363
- """)
364
-
365
- return app
366
-
367
-
368
- # =============================================================================
369
- # MAIN
370
- # =============================================================================
371
-
372
- def main():
373
- """Main entry point."""
374
- settings = get_settings()
375
-
376
- # Validate settings
377
- errors = settings.validate()
378
- if errors and not is_debug():
379
- print("Configuration errors:")
380
- for error in errors:
381
- print(f" - {error}")
382
- print("\nSet DEBUG=true to continue anyway.")
383
- return
384
-
385
- # Create and launch app
386
- app = create_ui()
387
- app.launch(
388
- server_port=settings.ui.server_port,
389
- share=settings.ui.share,
390
- debug=settings.debug,
391
- )
392
-
393
-
394
- if __name__ == "__main__":
395
- main()