riazmo commited on
Commit
c653719
Β·
verified Β·
1 Parent(s): 1bc5237

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +1069 -45
app.py CHANGED
@@ -849,6 +849,734 @@ def normalized_to_dict(normalized) -> dict:
849
  return result
850
 
851
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
852
  def build_analysis_status(final_recs: dict, cost_tracking: dict, errors: list) -> str:
853
  """Build status markdown from analysis results."""
854
 
@@ -2116,24 +2844,223 @@ def export_tokens_json():
2116
  # =============================================================================
2117
 
2118
  def create_ui():
2119
- """Create the Gradio interface."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2120
 
2121
  with gr.Blocks(
2122
  title="Design System Extractor v2",
2123
- theme=gr.themes.Soft(),
2124
- css="""
2125
- .color-swatch { display: inline-block; width: 24px; height: 24px; border-radius: 4px; margin-right: 8px; vertical-align: middle; }
2126
- """
2127
  ) as app:
2128
 
2129
- gr.Markdown("""
2130
- # 🎨 Design System Extractor v2
2131
-
2132
- **Reverse-engineer design systems from live websites.**
2133
-
2134
- A semi-automated, human-in-the-loop system that extracts, normalizes, and upgrades design tokens.
2135
-
2136
- ---
2137
  """)
2138
 
2139
  # =================================================================
@@ -2294,60 +3221,138 @@ def create_ui():
2294
  # STAGE 2: AI UPGRADES
2295
  # =================================================================
2296
 
2297
- with gr.Accordion("🧠 Stage 2: AI-Powered Upgrades", open=False) as stage2_accordion:
 
 
 
 
 
 
 
 
2298
 
2299
  stage2_status = gr.Markdown("Click 'Analyze' to start AI-powered design system analysis.")
2300
 
2301
  # =============================================================
2302
- # LLM CONFIGURATION & COMPETITORS
2303
  # =============================================================
2304
- with gr.Accordion("βš™οΈ Analysis Configuration", open=False):
 
 
2305
  gr.Markdown("""
2306
- ### πŸ€– LLM Models Used
2307
 
2308
- | Role | Model | Expertise |
2309
- |------|-------|-----------|
2310
- | **Typography Analyst** | meta-llama/Llama-3.1-70B | Type scale patterns, readability |
2311
- | **Color Analyst** | meta-llama/Llama-3.1-70B | Color theory, accessibility |
2312
- | **Spacing Analyst** | Rule-based | Grid alignment, consistency |
 
2313
 
2314
- *Analysis compares your design against industry leaders.*
2315
  """)
2316
 
2317
- gr.Markdown("### 🎯 Competitor Design Systems")
2318
- gr.Markdown("Enter design systems to compare against (comma-separated):")
2319
- competitors_input = gr.Textbox(
2320
- value="Material Design 3, Apple HIG, Shopify Polaris, IBM Carbon, Atlassian",
2321
- label="Competitors",
2322
- placeholder="Material Design 3, Apple HIG, Shopify Polaris...",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2323
  )
2324
- gr.Markdown("*Suggestions: Ant Design, Chakra UI, Tailwind, Bootstrap, Salesforce Lightning*")
2325
 
2326
- analyze_btn = gr.Button("πŸ€– Analyze Design System", variant="primary", size="lg")
 
 
 
 
 
 
 
 
 
2327
 
2328
- with gr.Accordion("πŸ“‹ AI Analysis Log", open=True):
2329
- stage2_log = gr.Textbox(label="Log", lines=18, interactive=False)
 
 
 
 
 
2330
 
2331
  # =============================================================
2332
- # BRAND COMPARISON (LLM Research)
2333
  # =============================================================
2334
  gr.Markdown("---")
2335
- brand_comparison = gr.Markdown("*Brand comparison will appear after analysis*")
2336
 
2337
  # =============================================================
2338
- # FONT FAMILIES DETECTED
2339
  # =============================================================
2340
  gr.Markdown("---")
2341
- gr.Markdown("## πŸ”€ Font Families Detected")
2342
- font_families_display = gr.Markdown("*Font information will appear after analysis*")
 
 
 
 
 
 
 
 
2343
 
2344
  # =============================================================
2345
- # TYPOGRAPHY SECTION - Desktop & Mobile
2346
  # =============================================================
2347
  gr.Markdown("---")
2348
  gr.Markdown("## πŸ“ Typography")
2349
 
2350
- # Visual Preview
2351
  with gr.Accordion("πŸ‘οΈ Typography Visual Preview", open=True):
2352
  stage2_typography_preview = gr.HTML(
2353
  value="<div style='padding: 20px; background: #f5f5f5; border-radius: 8px; color: #666;'>Typography preview will appear after analysis...</div>",
@@ -2541,11 +3546,28 @@ def create_ui():
2541
  outputs=[colors_table, typography_table, spacing_table],
2542
  )
2543
 
2544
- # Stage 2: Analyze
2545
- analyze_btn.click(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2546
  fn=run_stage2_analysis,
2547
- inputs=[competitors_input],
2548
- outputs=[stage2_status, stage2_log, brand_comparison, font_families_display,
2549
  typography_desktop, typography_mobile, spacing_comparison,
2550
  base_colors_display, color_ramps_display, radius_display, shadows_display,
2551
  stage2_typography_preview, stage2_color_ramps_preview,
@@ -2577,9 +3599,11 @@ def create_ui():
2577
 
2578
  gr.Markdown("""
2579
  ---
2580
- **Design System Extractor v2** | Built with Playwright + Gradio + LangGraph + HuggingFace
2581
 
2582
  *A semi-automated co-pilot for design system recovery and modernization.*
 
 
2583
  """)
2584
 
2585
  return app
 
849
  return result
850
 
851
 
852
+ # =============================================================================
853
+ # STAGE 2: NEW ARCHITECTURE (Rule Engine + Benchmark Research + LLM Agents)
854
+ # =============================================================================
855
+
856
+ async def run_stage2_analysis_v2(
857
+ selected_benchmarks: list[str] = None,
858
+ progress=gr.Progress()
859
+ ):
860
+ """
861
+ Run Stage 2 analysis with new architecture:
862
+ - Layer 1: Rule Engine (FREE)
863
+ - Layer 2: Benchmark Research (Firecrawl + Cache)
864
+ - Layer 3: LLM Agents (Brand ID, Benchmark Advisor, Best Practices)
865
+ - Layer 4: HEAD Synthesizer
866
+
867
+ Includes comprehensive error handling for graceful degradation.
868
+ """
869
+
870
+ # Validate Stage 1 completion
871
+ if not state.desktop_normalized or not state.mobile_normalized:
872
+ return create_stage2_error_response("❌ Please complete Stage 1 first")
873
+
874
+ # Default benchmarks if none selected
875
+ if not selected_benchmarks or len(selected_benchmarks) == 0:
876
+ selected_benchmarks = [
877
+ "material_design_3",
878
+ "shopify_polaris",
879
+ "atlassian_design",
880
+ ]
881
+
882
+ state.log("")
883
+ state.log("═" * 60)
884
+ state.log("πŸš€ STAGE 2: MULTI-AGENT ANALYSIS")
885
+ state.log("═" * 60)
886
+ state.log(f" Started: {datetime.now().strftime('%H:%M:%S')}")
887
+ state.log(f" Benchmarks: {', '.join(selected_benchmarks)}")
888
+ state.log("")
889
+
890
+ # Initialize results with defaults (for graceful degradation)
891
+ rule_results = None
892
+ benchmark_comparisons = []
893
+ brand_result = None
894
+ benchmark_advice = None
895
+ best_practices = None
896
+ final_synthesis = None
897
+
898
+ progress(0.05, desc="βš™οΈ Running Rule Engine...")
899
+
900
+ try:
901
+ # =================================================================
902
+ # LAYER 1: RULE ENGINE (FREE) - Critical, must succeed
903
+ # =================================================================
904
+ try:
905
+ from core.rule_engine import run_rule_engine
906
+
907
+ # Convert tokens to dict
908
+ desktop_dict = normalized_to_dict(state.desktop_normalized)
909
+ mobile_dict = normalized_to_dict(state.mobile_normalized)
910
+
911
+ # Validate we have data
912
+ if not desktop_dict.get("colors") and not desktop_dict.get("typography"):
913
+ raise ValueError("No tokens extracted from Stage 1")
914
+
915
+ # Run rule engine
916
+ rule_results = run_rule_engine(
917
+ typography_tokens=desktop_dict.get("typography", {}),
918
+ color_tokens=desktop_dict.get("colors", {}),
919
+ spacing_tokens=desktop_dict.get("spacing", {}),
920
+ radius_tokens=desktop_dict.get("radius", {}),
921
+ shadow_tokens=desktop_dict.get("shadows", {}),
922
+ log_callback=state.log,
923
+ )
924
+
925
+ state.rule_engine_results = rule_results
926
+ state.log("")
927
+ state.log(" βœ… Rule Engine: SUCCESS")
928
+
929
+ except Exception as e:
930
+ state.log(f" ❌ Rule Engine FAILED: {str(e)[:100]}")
931
+ state.log(" └─ Cannot proceed without rule engine results")
932
+ import traceback
933
+ state.log(traceback.format_exc()[:500])
934
+ return create_stage2_error_response(f"❌ Rule Engine failed: {str(e)}")
935
+
936
+ progress(0.20, desc="πŸ”¬ Researching benchmarks...")
937
+
938
+ # =================================================================
939
+ # LAYER 2: BENCHMARK RESEARCH - Can use fallback
940
+ # =================================================================
941
+ try:
942
+ from agents.benchmark_researcher import BenchmarkResearcher, FALLBACK_BENCHMARKS, BenchmarkData
943
+
944
+ # Try to get Firecrawl client (optional)
945
+ firecrawl_client = None
946
+ try:
947
+ from agents.firecrawl_extractor import get_firecrawl_client
948
+ firecrawl_client = get_firecrawl_client()
949
+ state.log(" β”œβ”€ Firecrawl client: Available")
950
+ except Exception as fc_err:
951
+ state.log(f" β”œβ”€ Firecrawl client: Not available ({str(fc_err)[:30]})")
952
+ state.log(" β”‚ └─ Will use cached/fallback data")
953
+
954
+ # Get HF client for LLM extraction (optional)
955
+ hf_client = None
956
+ try:
957
+ from core.hf_inference import get_inference_client
958
+ hf_client = get_inference_client()
959
+ state.log(" β”œβ”€ HF client: Available")
960
+ except Exception as hf_err:
961
+ state.log(f" β”œβ”€ HF client: Not available ({str(hf_err)[:30]})")
962
+
963
+ researcher = BenchmarkResearcher(
964
+ firecrawl_client=firecrawl_client,
965
+ hf_client=hf_client,
966
+ )
967
+
968
+ # Research selected benchmarks (with fallback)
969
+ try:
970
+ benchmarks = await researcher.research_selected_benchmarks(
971
+ selected_keys=selected_benchmarks,
972
+ log_callback=state.log,
973
+ )
974
+ except Exception as research_err:
975
+ state.log(f" ⚠️ Research failed, using fallback: {str(research_err)[:50]}")
976
+ # Use fallback data
977
+ benchmarks = []
978
+ for key in selected_benchmarks:
979
+ if key in FALLBACK_BENCHMARKS:
980
+ data = FALLBACK_BENCHMARKS[key]
981
+ benchmarks.append(BenchmarkData(
982
+ key=key,
983
+ name=key.replace("_", " ").title(),
984
+ short_name=key.split("_")[0].title(),
985
+ vendor="",
986
+ icon="πŸ“¦",
987
+ typography=data.get("typography", {}),
988
+ spacing=data.get("spacing", {}),
989
+ colors=data.get("colors", {}),
990
+ fetched_at=datetime.now().isoformat(),
991
+ confidence="fallback",
992
+ best_for=[],
993
+ ))
994
+
995
+ # Compare to benchmarks
996
+ if benchmarks and rule_results:
997
+ benchmark_comparisons = researcher.compare_to_benchmarks(
998
+ your_ratio=rule_results.typography.detected_ratio,
999
+ your_base_size=int(rule_results.typography.sizes_px[0]) if rule_results.typography.sizes_px else 16,
1000
+ your_spacing_grid=rule_results.spacing.detected_base,
1001
+ benchmarks=benchmarks,
1002
+ log_callback=state.log,
1003
+ )
1004
+ state.benchmark_comparisons = benchmark_comparisons
1005
+ state.log("")
1006
+ state.log(f" βœ… Benchmark Research: SUCCESS ({len(benchmarks)} systems)")
1007
+ else:
1008
+ state.log(" ⚠️ No benchmarks available for comparison")
1009
+
1010
+ except Exception as e:
1011
+ state.log(f" ⚠️ Benchmark Research FAILED: {str(e)[:100]}")
1012
+ state.log(" └─ Continuing without benchmark comparison...")
1013
+ benchmark_comparisons = []
1014
+
1015
+ progress(0.40, desc="πŸ€– Running LLM Agents...")
1016
+
1017
+ # =================================================================
1018
+ # LAYER 3: LLM AGENTS - Can fail gracefully
1019
+ # =================================================================
1020
+ try:
1021
+ from agents.llm_agents import (
1022
+ BrandIdentifierAgent,
1023
+ BenchmarkAdvisorAgent,
1024
+ BestPracticesValidatorAgent,
1025
+ BrandIdentification,
1026
+ BenchmarkAdvice,
1027
+ BestPracticesResult,
1028
+ )
1029
+
1030
+ state.log("")
1031
+ state.log("═" * 60)
1032
+ state.log("πŸ€– LAYER 3: LLM ANALYSIS")
1033
+ state.log("═" * 60)
1034
+
1035
+ # Check if HF client is available
1036
+ if not hf_client:
1037
+ try:
1038
+ from core.hf_inference import get_inference_client
1039
+ hf_client = get_inference_client()
1040
+ except Exception:
1041
+ state.log(" ⚠️ HF client not available - skipping LLM agents")
1042
+ hf_client = None
1043
+
1044
+ if hf_client:
1045
+ # Initialize agents
1046
+ brand_agent = BrandIdentifierAgent(hf_client)
1047
+ benchmark_agent = BenchmarkAdvisorAgent(hf_client)
1048
+ best_practices_agent = BestPracticesValidatorAgent(hf_client)
1049
+
1050
+ # Get semantic analysis from Stage 1
1051
+ semantic_analysis = getattr(state, 'semantic_analysis', {})
1052
+ desktop_dict = normalized_to_dict(state.desktop_normalized)
1053
+
1054
+ # Run agents (with individual error handling)
1055
+ # Brand Identifier
1056
+ try:
1057
+ brand_result = await brand_agent.analyze(
1058
+ color_tokens=desktop_dict.get("colors", {}),
1059
+ semantic_analysis=semantic_analysis,
1060
+ log_callback=state.log,
1061
+ )
1062
+ except Exception as e:
1063
+ state.log(f" ⚠️ Brand Identifier failed: {str(e)[:50]}")
1064
+ brand_result = BrandIdentification()
1065
+
1066
+ # Benchmark Advisor
1067
+ if benchmark_comparisons:
1068
+ try:
1069
+ benchmark_advice = await benchmark_agent.analyze(
1070
+ user_ratio=rule_results.typography.detected_ratio,
1071
+ user_base=int(rule_results.typography.sizes_px[0]) if rule_results.typography.sizes_px else 16,
1072
+ user_spacing=rule_results.spacing.detected_base,
1073
+ benchmark_comparisons=benchmark_comparisons,
1074
+ log_callback=state.log,
1075
+ )
1076
+ except Exception as e:
1077
+ state.log(f" ⚠️ Benchmark Advisor failed: {str(e)[:50]}")
1078
+ benchmark_advice = BenchmarkAdvice()
1079
+ else:
1080
+ benchmark_advice = BenchmarkAdvice()
1081
+
1082
+ # Best Practices Validator
1083
+ try:
1084
+ best_practices = await best_practices_agent.analyze(
1085
+ rule_engine_results=rule_results,
1086
+ log_callback=state.log,
1087
+ )
1088
+ except Exception as e:
1089
+ state.log(f" ⚠️ Best Practices Validator failed: {str(e)[:50]}")
1090
+ best_practices = BestPracticesResult(overall_score=rule_results.consistency_score)
1091
+ else:
1092
+ # No HF client - use defaults
1093
+ state.log(" └─ Using default values (no LLM)")
1094
+ brand_result = BrandIdentification()
1095
+ benchmark_advice = BenchmarkAdvice()
1096
+ best_practices = BestPracticesResult(overall_score=rule_results.consistency_score)
1097
+
1098
+ except Exception as e:
1099
+ state.log(f" ⚠️ LLM Agents FAILED: {str(e)[:100]}")
1100
+ brand_result = BrandIdentification() if not brand_result else brand_result
1101
+ benchmark_advice = BenchmarkAdvice() if not benchmark_advice else benchmark_advice
1102
+ best_practices = BestPracticesResult(overall_score=rule_results.consistency_score if rule_results else 50)
1103
+
1104
+ progress(0.70, desc="🧠 Synthesizing results...")
1105
+
1106
+ # =================================================================
1107
+ # LAYER 4: HEAD SYNTHESIZER - Can use fallback
1108
+ # =================================================================
1109
+ try:
1110
+ from agents.llm_agents import HeadSynthesizerAgent, HeadSynthesis
1111
+
1112
+ if hf_client and brand_result and benchmark_advice and best_practices:
1113
+ head_agent = HeadSynthesizerAgent(hf_client)
1114
+
1115
+ try:
1116
+ final_synthesis = await head_agent.synthesize(
1117
+ rule_engine_results=rule_results,
1118
+ benchmark_comparisons=benchmark_comparisons,
1119
+ brand_identification=brand_result,
1120
+ benchmark_advice=benchmark_advice,
1121
+ best_practices=best_practices,
1122
+ log_callback=state.log,
1123
+ )
1124
+ except Exception as e:
1125
+ state.log(f" ⚠️ HEAD Synthesizer failed: {str(e)[:50]}")
1126
+ final_synthesis = None
1127
+
1128
+ # Create fallback synthesis if needed
1129
+ if not final_synthesis:
1130
+ state.log(" └─ Creating fallback synthesis...")
1131
+ final_synthesis = create_fallback_synthesis(
1132
+ rule_results, benchmark_comparisons, brand_result, best_practices
1133
+ )
1134
+
1135
+ state.final_synthesis = final_synthesis
1136
+
1137
+ except Exception as e:
1138
+ state.log(f" ⚠️ Synthesis FAILED: {str(e)[:100]}")
1139
+ final_synthesis = create_fallback_synthesis(
1140
+ rule_results, benchmark_comparisons, brand_result, best_practices
1141
+ )
1142
+ state.final_synthesis = final_synthesis
1143
+
1144
+ progress(0.85, desc="πŸ“Š Formatting results...")
1145
+
1146
+ # =================================================================
1147
+ # FORMAT OUTPUTS FOR UI
1148
+ # =================================================================
1149
+
1150
+ try:
1151
+ # Build status markdown
1152
+ status_md = format_stage2_status_v2(
1153
+ rule_results=rule_results,
1154
+ final_synthesis=final_synthesis,
1155
+ best_practices=best_practices,
1156
+ )
1157
+
1158
+ # Build benchmark comparison HTML
1159
+ benchmark_md = format_benchmark_comparison_v2(
1160
+ benchmark_comparisons=benchmark_comparisons,
1161
+ benchmark_advice=benchmark_advice,
1162
+ )
1163
+
1164
+ # Build scores dashboard HTML
1165
+ scores_html = format_scores_dashboard_v2(
1166
+ rule_results=rule_results,
1167
+ final_synthesis=final_synthesis,
1168
+ best_practices=best_practices,
1169
+ )
1170
+
1171
+ # Build priority actions HTML
1172
+ actions_html = format_priority_actions_v2(
1173
+ rule_results=rule_results,
1174
+ final_synthesis=final_synthesis,
1175
+ best_practices=best_practices,
1176
+ )
1177
+
1178
+ # Build color recommendations table
1179
+ color_recs_table = format_color_recommendations_table_v2(
1180
+ rule_results=rule_results,
1181
+ brand_result=brand_result,
1182
+ final_synthesis=final_synthesis,
1183
+ )
1184
+
1185
+ # Get fonts and typography data
1186
+ fonts = get_detected_fonts()
1187
+ base_size = get_base_font_size()
1188
+
1189
+ typography_desktop_data = format_typography_comparison_viewport(
1190
+ state.desktop_normalized, base_size, "desktop"
1191
+ )
1192
+ typography_mobile_data = format_typography_comparison_viewport(
1193
+ state.mobile_normalized, base_size, "mobile"
1194
+ )
1195
+
1196
+ # Generate visual previews
1197
+ typography_preview_html = ""
1198
+ try:
1199
+ from core.preview_generator import generate_typography_preview_html
1200
+
1201
+ primary_font = fonts.get("primary", "Open Sans")
1202
+ desktop_typo_dict = {
1203
+ name: {
1204
+ "font_size": t.font_size,
1205
+ "font_weight": t.font_weight,
1206
+ "line_height": t.line_height,
1207
+ }
1208
+ for name, t in state.desktop_normalized.typography.items()
1209
+ }
1210
+ typography_preview_html = generate_typography_preview_html(desktop_typo_dict, primary_font)
1211
+ except Exception as preview_err:
1212
+ state.log(f" ⚠️ Preview generation failed: {str(preview_err)[:50]}")
1213
+ typography_preview_html = "<div style='padding:20px;background:#f5f5f5;'>Preview unavailable</div>"
1214
+
1215
+ except Exception as format_err:
1216
+ state.log(f" ⚠️ Formatting failed: {str(format_err)[:100]}")
1217
+ # Return minimal results
1218
+ return (
1219
+ f"⚠️ Analysis completed with formatting errors: {str(format_err)[:50]}",
1220
+ state.get_logs(),
1221
+ "*Benchmark comparison unavailable*",
1222
+ "<div>Scores unavailable</div>",
1223
+ "<div>Actions unavailable</div>",
1224
+ [],
1225
+ None,
1226
+ None,
1227
+ "",
1228
+ )
1229
+
1230
+ progress(0.95, desc="βœ… Complete!")
1231
+
1232
+ # Final log summary
1233
+ state.log("")
1234
+ state.log("═" * 60)
1235
+ state.log("πŸ“Š FINAL RESULTS")
1236
+ state.log("═" * 60)
1237
+ state.log("")
1238
+ overall_score = final_synthesis.scores.get('overall', rule_results.consistency_score) if final_synthesis else rule_results.consistency_score
1239
+ state.log(f" 🎯 OVERALL SCORE: {overall_score}/100")
1240
+ if final_synthesis and final_synthesis.scores:
1241
+ state.log(f" β”œβ”€ Accessibility: {final_synthesis.scores.get('accessibility', '?')}/100")
1242
+ state.log(f" β”œβ”€ Consistency: {final_synthesis.scores.get('consistency', '?')}/100")
1243
+ state.log(f" └─ Organization: {final_synthesis.scores.get('organization', '?')}/100")
1244
+ state.log("")
1245
+ if benchmark_comparisons:
1246
+ state.log(f" πŸ† Closest Benchmark: {benchmark_comparisons[0].benchmark.name if benchmark_comparisons else 'N/A'}")
1247
+ state.log("")
1248
+ state.log(" 🎯 TOP 3 ACTIONS:")
1249
+ if final_synthesis and final_synthesis.top_3_actions:
1250
+ for i, action in enumerate(final_synthesis.top_3_actions[:3]):
1251
+ impact = action.get('impact', 'medium')
1252
+ icon = "πŸ”΄" if impact == "high" else "🟑" if impact == "medium" else "🟒"
1253
+ state.log(f" β”‚ {i+1}. {icon} {action.get('action', 'N/A')}")
1254
+ else:
1255
+ state.log(f" β”‚ 1. πŸ”΄ Fix {rule_results.aa_failures} AA compliance failures")
1256
+ state.log("")
1257
+ state.log("═" * 60)
1258
+ state.log(f" πŸ’° TOTAL COST: ~$0.003")
1259
+ state.log(f" ⏱️ COMPLETED: {datetime.now().strftime('%H:%M:%S')}")
1260
+ state.log("═" * 60)
1261
+
1262
+ return (
1263
+ status_md,
1264
+ state.get_logs(),
1265
+ benchmark_md,
1266
+ scores_html,
1267
+ actions_html,
1268
+ color_recs_table,
1269
+ typography_desktop_data,
1270
+ typography_mobile_data,
1271
+ typography_preview_html,
1272
+ )
1273
+
1274
+ except Exception as e:
1275
+ import traceback
1276
+ state.log(f"❌ Critical Error: {str(e)}")
1277
+ state.log(traceback.format_exc())
1278
+ return create_stage2_error_response(f"❌ Analysis failed: {str(e)}")
1279
+
1280
+
1281
+ def create_fallback_synthesis(rule_results, benchmark_comparisons, brand_result, best_practices):
1282
+ """Create a fallback synthesis when LLM synthesis fails."""
1283
+ from agents.llm_agents import HeadSynthesis
1284
+
1285
+ # Calculate scores from rule engine
1286
+ overall = rule_results.consistency_score if rule_results else 50
1287
+ accessibility = max(0, 100 - (rule_results.aa_failures * 10)) if rule_results else 50
1288
+
1289
+ # Build actions from rule engine
1290
+ actions = []
1291
+ if rule_results and rule_results.aa_failures > 0:
1292
+ actions.append({
1293
+ "action": f"Fix {rule_results.aa_failures} colors failing AA compliance",
1294
+ "impact": "high",
1295
+ "effort": "30 min",
1296
+ })
1297
+ if rule_results and not rule_results.typography.is_consistent:
1298
+ actions.append({
1299
+ "action": f"Align type scale to {rule_results.typography.recommendation} ({rule_results.typography.recommendation_name})",
1300
+ "impact": "medium",
1301
+ "effort": "1 hour",
1302
+ })
1303
+ if rule_results and rule_results.color_stats.unique_count > 30:
1304
+ actions.append({
1305
+ "action": f"Consolidate {rule_results.color_stats.unique_count} colors to ~15 semantic colors",
1306
+ "impact": "medium",
1307
+ "effort": "2 hours",
1308
+ })
1309
+
1310
+ return HeadSynthesis(
1311
+ executive_summary=f"Your design system scores {overall}/100. Analysis completed with fallback synthesis.",
1312
+ scores={
1313
+ "overall": overall,
1314
+ "accessibility": accessibility,
1315
+ "consistency": overall,
1316
+ "organization": 50,
1317
+ },
1318
+ benchmark_fit={
1319
+ "closest": benchmark_comparisons[0].benchmark.name if benchmark_comparisons else "Unknown",
1320
+ "similarity": f"{benchmark_comparisons[0].overall_match_pct:.0f}%" if benchmark_comparisons else "N/A",
1321
+ },
1322
+ brand_analysis={
1323
+ "primary": brand_result.brand_primary.get("color", "Unknown") if brand_result else "Unknown",
1324
+ "cohesion": brand_result.cohesion_score if brand_result else 5,
1325
+ },
1326
+ top_3_actions=actions[:3],
1327
+ color_recommendations=[],
1328
+ type_scale_recommendation={
1329
+ "current_ratio": rule_results.typography.detected_ratio if rule_results else 1.0,
1330
+ "recommended_ratio": rule_results.typography.recommendation if rule_results else 1.25,
1331
+ },
1332
+ spacing_recommendation={
1333
+ "current": f"{rule_results.spacing.detected_base}px" if rule_results else "Unknown",
1334
+ "recommended": f"{rule_results.spacing.recommendation}px" if rule_results else "8px",
1335
+ },
1336
+ )
1337
+
1338
+
1339
+ def create_stage2_error_response(error_msg: str):
1340
+ """Create error response tuple for Stage 2."""
1341
+ return (
1342
+ error_msg,
1343
+ state.get_logs(),
1344
+ "", # benchmark_md
1345
+ "", # scores_html
1346
+ "", # actions_html
1347
+ [], # color_recs_table
1348
+ None, # typography_desktop
1349
+ None, # typography_mobile
1350
+ "", # typography_preview
1351
+ )
1352
+
1353
+
1354
+ def format_stage2_status_v2(rule_results, final_synthesis, best_practices) -> str:
1355
+ """Format Stage 2 status with new architecture results."""
1356
+
1357
+ lines = []
1358
+ lines.append("## βœ… Analysis Complete!")
1359
+ lines.append("")
1360
+
1361
+ # Overall Score
1362
+ overall = final_synthesis.scores.get('overall', rule_results.consistency_score)
1363
+ lines.append(f"### 🎯 Overall Score: {overall}/100")
1364
+ lines.append("")
1365
+
1366
+ # Executive Summary
1367
+ if final_synthesis.executive_summary:
1368
+ lines.append(f"*{final_synthesis.executive_summary}*")
1369
+ lines.append("")
1370
+
1371
+ # Quick Stats
1372
+ lines.append("### πŸ“Š Quick Stats")
1373
+ lines.append(f"- **AA Failures:** {rule_results.aa_failures}")
1374
+ lines.append(f"- **Type Scale:** {rule_results.typography.detected_ratio:.3f} ({rule_results.typography.scale_name})")
1375
+ lines.append(f"- **Spacing Grid:** {rule_results.spacing.detected_base}px ({rule_results.spacing.alignment_percentage:.0f}% aligned)")
1376
+ lines.append(f"- **Unique Colors:** {rule_results.color_stats.unique_count}")
1377
+ lines.append("")
1378
+
1379
+ # Cost
1380
+ lines.append("### πŸ’° Cost")
1381
+ lines.append("**Total:** ~$0.003 (Rule Engine: $0 + LLM: ~$0.003)")
1382
+
1383
+ return "\n".join(lines)
1384
+
1385
+
1386
+ def format_benchmark_comparison_v2(benchmark_comparisons, benchmark_advice) -> str:
1387
+ """Format benchmark comparison results."""
1388
+
1389
+ if not benchmark_comparisons:
1390
+ return "*No benchmark comparison available*"
1391
+
1392
+ lines = []
1393
+ lines.append("## πŸ“Š Benchmark Comparison")
1394
+ lines.append("")
1395
+
1396
+ # Recommended benchmark
1397
+ if benchmark_advice and benchmark_advice.recommended_benchmark_name:
1398
+ lines.append(f"### πŸ† Recommended: {benchmark_advice.recommended_benchmark_name}")
1399
+ if benchmark_advice.reasoning:
1400
+ lines.append(f"*{benchmark_advice.reasoning[:200]}*")
1401
+ lines.append("")
1402
+
1403
+ # Comparison table
1404
+ lines.append("### πŸ“ˆ Similarity Ranking")
1405
+ lines.append("")
1406
+ lines.append("| Rank | Design System | Match | Type Ratio | Base | Grid |")
1407
+ lines.append("|------|---------------|-------|------------|------|------|")
1408
+
1409
+ medals = ["πŸ₯‡", "πŸ₯ˆ", "πŸ₯‰"]
1410
+ for i, c in enumerate(benchmark_comparisons[:5]):
1411
+ medal = medals[i] if i < 3 else str(i+1)
1412
+ b = c.benchmark
1413
+ lines.append(
1414
+ f"| {medal} | {b.icon} {b.short_name} | {c.overall_match_pct:.0f}% | "
1415
+ f"{b.typography.get('scale_ratio', '?')} | {b.typography.get('base_size', '?')}px | "
1416
+ f"{b.spacing.get('base', '?')}px |"
1417
+ )
1418
+
1419
+ lines.append("")
1420
+
1421
+ # Alignment changes needed
1422
+ if benchmark_advice and benchmark_advice.alignment_changes:
1423
+ lines.append("### πŸ”§ Changes to Align")
1424
+ for change in benchmark_advice.alignment_changes[:3]:
1425
+ lines.append(f"- **{change.get('change', '?')}**: {change.get('from', '?')} β†’ {change.get('to', '?')} (effort: {change.get('effort', '?')})")
1426
+
1427
+ return "\n".join(lines)
1428
+
1429
+
1430
+ def format_scores_dashboard_v2(rule_results, final_synthesis, best_practices) -> str:
1431
+ """Format scores dashboard HTML."""
1432
+
1433
+ overall = final_synthesis.scores.get('overall', rule_results.consistency_score)
1434
+ accessibility = final_synthesis.scores.get('accessibility', 100 - (rule_results.aa_failures * 5))
1435
+ consistency = final_synthesis.scores.get('consistency', rule_results.consistency_score)
1436
+ organization = final_synthesis.scores.get('organization', 50)
1437
+
1438
+ def score_color(score):
1439
+ if score >= 80:
1440
+ return "#10b981" # Green
1441
+ elif score >= 60:
1442
+ return "#f59e0b" # Yellow
1443
+ else:
1444
+ return "#ef4444" # Red
1445
+
1446
+ html = f"""
1447
+ <div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin: 20px 0;">
1448
+ <div style="background: linear-gradient(135deg, {score_color(overall)}22 0%, {score_color(overall)}11 100%);
1449
+ border: 2px solid {score_color(overall)}; border-radius: 12px; padding: 20px; text-align: center;">
1450
+ <div style="font-size: 32px; font-weight: 700; color: {score_color(overall)};">{overall}</div>
1451
+ <div style="font-size: 12px; color: #64748b; margin-top: 4px;">OVERALL</div>
1452
+ </div>
1453
+ <div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; text-align: center;">
1454
+ <div style="font-size: 24px; font-weight: 600; color: {score_color(accessibility)};">{accessibility}</div>
1455
+ <div style="font-size: 12px; color: #64748b; margin-top: 4px;">Accessibility</div>
1456
+ </div>
1457
+ <div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; text-align: center;">
1458
+ <div style="font-size: 24px; font-weight: 600; color: {score_color(consistency)};">{consistency}</div>
1459
+ <div style="font-size: 12px; color: #64748b; margin-top: 4px;">Consistency</div>
1460
+ </div>
1461
+ <div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; text-align: center;">
1462
+ <div style="font-size: 24px; font-weight: 600; color: {score_color(organization)};">{organization}</div>
1463
+ <div style="font-size: 12px; color: #64748b; margin-top: 4px;">Organization</div>
1464
+ </div>
1465
+ </div>
1466
+ """
1467
+
1468
+ return html
1469
+
1470
+
1471
+ def format_priority_actions_v2(rule_results, final_synthesis, best_practices) -> str:
1472
+ """Format priority actions HTML."""
1473
+
1474
+ actions = final_synthesis.top_3_actions if final_synthesis.top_3_actions else []
1475
+
1476
+ # If no synthesis actions, build from rule engine
1477
+ if not actions and best_practices and best_practices.priority_fixes:
1478
+ actions = best_practices.priority_fixes
1479
+
1480
+ if not actions:
1481
+ # Default actions from rule engine
1482
+ actions = []
1483
+ if rule_results.aa_failures > 0:
1484
+ actions.append({
1485
+ "action": f"Fix {rule_results.aa_failures} colors failing AA compliance",
1486
+ "impact": "high",
1487
+ "effort": "30 min",
1488
+ })
1489
+ if not rule_results.typography.is_consistent:
1490
+ actions.append({
1491
+ "action": f"Align type scale to {rule_results.typography.recommendation} ({rule_results.typography.recommendation_name})",
1492
+ "impact": "medium",
1493
+ "effort": "1 hour",
1494
+ })
1495
+ if rule_results.color_stats.unique_count > 30:
1496
+ actions.append({
1497
+ "action": f"Consolidate {rule_results.color_stats.unique_count} colors to ~15 semantic colors",
1498
+ "impact": "medium",
1499
+ "effort": "2 hours",
1500
+ })
1501
+
1502
+ html_items = []
1503
+ for i, action in enumerate(actions[:3]):
1504
+ impact = action.get('impact', 'medium')
1505
+ border_color = "#ef4444" if impact == "high" else "#f59e0b" if impact == "medium" else "#10b981"
1506
+ impact_bg = "#fee2e2" if impact == "high" else "#fef3c7" if impact == "medium" else "#dcfce7"
1507
+ impact_text = "#991b1b" if impact == "high" else "#92400e" if impact == "medium" else "#166534"
1508
+ icon = "πŸ”΄" if impact == "high" else "🟑" if impact == "medium" else "🟒"
1509
+
1510
+ html_items.append(f"""
1511
+ <div style="background: white; border: 1px solid #e2e8f0; border-left: 4px solid {border_color};
1512
+ border-radius: 8px; padding: 16px; margin-bottom: 12px;">
1513
+ <div style="display: flex; justify-content: space-between; align-items: flex-start;">
1514
+ <div>
1515
+ <div style="font-weight: 600; color: #1e293b; margin-bottom: 4px;">
1516
+ {icon} {action.get('action', 'N/A')}
1517
+ </div>
1518
+ <div style="font-size: 13px; color: #64748b;">
1519
+ {action.get('details', '')}
1520
+ </div>
1521
+ </div>
1522
+ <div style="display: flex; gap: 8px;">
1523
+ <span style="background: {impact_bg}; color: {impact_text}; padding: 4px 8px;
1524
+ border-radius: 12px; font-size: 11px; font-weight: 600;">
1525
+ {impact.upper()}
1526
+ </span>
1527
+ <span style="background: #f1f5f9; color: #475569; padding: 4px 8px;
1528
+ border-radius: 12px; font-size: 11px;">
1529
+ {action.get('effort', '?')}
1530
+ </span>
1531
+ </div>
1532
+ </div>
1533
+ </div>
1534
+ """)
1535
+
1536
+ return f"""
1537
+ <div style="margin: 20px 0;">
1538
+ <h3 style="margin-bottom: 16px; color: #1e293b;">🎯 Priority Actions</h3>
1539
+ {''.join(html_items)}
1540
+ </div>
1541
+ """
1542
+
1543
+
1544
+ def format_color_recommendations_table_v2(rule_results, brand_result, final_synthesis) -> list:
1545
+ """Format color recommendations as table data."""
1546
+
1547
+ rows = []
1548
+
1549
+ # Add AA failures with fixes
1550
+ for a in rule_results.accessibility:
1551
+ if not a.passes_aa_normal and a.suggested_fix:
1552
+ role = "brand.primary" if brand_result and brand_result.brand_primary.get("color") == a.hex_color else a.name
1553
+ rows.append([
1554
+ True, # Accept checkbox
1555
+ role,
1556
+ a.hex_color,
1557
+ f"Fails AA ({a.contrast_on_white:.1f}:1)",
1558
+ a.suggested_fix,
1559
+ f"{a.suggested_fix_contrast:.1f}:1",
1560
+ ])
1561
+
1562
+ # Add recommendations from synthesis
1563
+ if final_synthesis and final_synthesis.color_recommendations:
1564
+ for rec in final_synthesis.color_recommendations:
1565
+ if rec.get("current") != rec.get("suggested"):
1566
+ # Check if not already in rows
1567
+ if not any(r[2] == rec.get("current") for r in rows):
1568
+ rows.append([
1569
+ rec.get("accept", True),
1570
+ rec.get("role", "unknown"),
1571
+ rec.get("current", ""),
1572
+ rec.get("reason", ""),
1573
+ rec.get("suggested", ""),
1574
+ "",
1575
+ ])
1576
+
1577
+ return rows
1578
+
1579
+
1580
  def build_analysis_status(final_recs: dict, cost_tracking: dict, errors: list) -> str:
1581
  """Build status markdown from analysis results."""
1582
 
 
2844
  # =============================================================================
2845
 
2846
  def create_ui():
2847
+ """Create the Gradio interface with corporate branding."""
2848
+
2849
+ # Corporate theme customization
2850
+ corporate_theme = gr.themes.Base(
2851
+ primary_hue=gr.themes.colors.blue,
2852
+ secondary_hue=gr.themes.colors.slate,
2853
+ neutral_hue=gr.themes.colors.slate,
2854
+ font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui", "sans-serif"],
2855
+ font_mono=[gr.themes.GoogleFont("JetBrains Mono"), "ui-monospace", "monospace"],
2856
+ ).set(
2857
+ # Colors
2858
+ body_background_fill="#f8fafc",
2859
+ body_background_fill_dark="#0f172a",
2860
+ block_background_fill="white",
2861
+ block_background_fill_dark="#1e293b",
2862
+ block_border_color="#e2e8f0",
2863
+ block_border_color_dark="#334155",
2864
+ block_label_background_fill="#f1f5f9",
2865
+ block_label_background_fill_dark="#1e293b",
2866
+ block_title_text_color="#0f172a",
2867
+ block_title_text_color_dark="#f1f5f9",
2868
+
2869
+ # Primary button
2870
+ button_primary_background_fill="#2563eb",
2871
+ button_primary_background_fill_hover="#1d4ed8",
2872
+ button_primary_text_color="white",
2873
+
2874
+ # Secondary button
2875
+ button_secondary_background_fill="#f1f5f9",
2876
+ button_secondary_background_fill_hover="#e2e8f0",
2877
+ button_secondary_text_color="#1e293b",
2878
+
2879
+ # Input fields
2880
+ input_background_fill="#ffffff",
2881
+ input_background_fill_dark="#1e293b",
2882
+ input_border_color="#cbd5e1",
2883
+ input_border_color_dark="#475569",
2884
+
2885
+ # Shadows and radius
2886
+ block_shadow="0 1px 3px rgba(0,0,0,0.1)",
2887
+ block_shadow_dark="0 1px 3px rgba(0,0,0,0.3)",
2888
+ block_border_width="1px",
2889
+ block_radius="8px",
2890
+
2891
+ # Text
2892
+ body_text_color="#1e293b",
2893
+ body_text_color_dark="#e2e8f0",
2894
+ body_text_size="14px",
2895
+ )
2896
+
2897
+ # Custom CSS for additional styling
2898
+ custom_css = """
2899
+ /* Global styles */
2900
+ .gradio-container {
2901
+ max-width: 1400px !important;
2902
+ margin: 0 auto !important;
2903
+ }
2904
+
2905
+ /* Header branding */
2906
+ .app-header {
2907
+ background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
2908
+ padding: 24px 32px;
2909
+ border-radius: 12px;
2910
+ margin-bottom: 24px;
2911
+ color: white;
2912
+ }
2913
+ .app-header h1 {
2914
+ margin: 0 0 8px 0;
2915
+ font-size: 28px;
2916
+ font-weight: 700;
2917
+ }
2918
+ .app-header p {
2919
+ margin: 0;
2920
+ opacity: 0.9;
2921
+ font-size: 14px;
2922
+ }
2923
+
2924
+ /* Stage indicators */
2925
+ .stage-header {
2926
+ background: linear-gradient(90deg, #f1f5f9 0%, #ffffff 100%);
2927
+ padding: 16px 20px;
2928
+ border-radius: 8px;
2929
+ border-left: 4px solid #2563eb;
2930
+ margin-bottom: 16px;
2931
+ }
2932
+ .stage-header h2 {
2933
+ margin: 0;
2934
+ font-size: 18px;
2935
+ color: #1e293b;
2936
+ }
2937
+
2938
+ /* Log styling */
2939
+ .log-container textarea {
2940
+ font-family: 'JetBrains Mono', monospace !important;
2941
+ font-size: 12px !important;
2942
+ line-height: 1.6 !important;
2943
+ background: #0f172a !important;
2944
+ color: #e2e8f0 !important;
2945
+ border-radius: 8px !important;
2946
+ }
2947
+
2948
+ /* Color swatch */
2949
+ .color-swatch {
2950
+ display: inline-block;
2951
+ width: 24px;
2952
+ height: 24px;
2953
+ border-radius: 4px;
2954
+ margin-right: 8px;
2955
+ vertical-align: middle;
2956
+ border: 1px solid rgba(0,0,0,0.1);
2957
+ }
2958
+
2959
+ /* Score badges */
2960
+ .score-badge {
2961
+ display: inline-block;
2962
+ padding: 4px 12px;
2963
+ border-radius: 20px;
2964
+ font-weight: 600;
2965
+ font-size: 13px;
2966
+ }
2967
+ .score-badge.high { background: #dcfce7; color: #166534; }
2968
+ .score-badge.medium { background: #fef3c7; color: #92400e; }
2969
+ .score-badge.low { background: #fee2e2; color: #991b1b; }
2970
+
2971
+ /* Benchmark cards */
2972
+ .benchmark-card {
2973
+ background: #f8fafc;
2974
+ border: 1px solid #e2e8f0;
2975
+ border-radius: 8px;
2976
+ padding: 16px;
2977
+ margin-bottom: 12px;
2978
+ }
2979
+ .benchmark-card.selected {
2980
+ border-color: #2563eb;
2981
+ background: #eff6ff;
2982
+ }
2983
+
2984
+ /* Action items */
2985
+ .action-item {
2986
+ background: white;
2987
+ border: 1px solid #e2e8f0;
2988
+ border-radius: 8px;
2989
+ padding: 16px;
2990
+ margin-bottom: 8px;
2991
+ }
2992
+ .action-item.high-priority {
2993
+ border-left: 4px solid #ef4444;
2994
+ }
2995
+ .action-item.medium-priority {
2996
+ border-left: 4px solid #f59e0b;
2997
+ }
2998
+
2999
+ /* Progress indicator */
3000
+ .progress-bar {
3001
+ height: 4px;
3002
+ background: #e2e8f0;
3003
+ border-radius: 2px;
3004
+ overflow: hidden;
3005
+ }
3006
+ .progress-bar-fill {
3007
+ height: 100%;
3008
+ background: linear-gradient(90deg, #2563eb, #3b82f6);
3009
+ transition: width 0.3s ease;
3010
+ }
3011
+
3012
+ /* Accordion styling */
3013
+ .accordion-header {
3014
+ font-weight: 600 !important;
3015
+ }
3016
+
3017
+ /* Table styling */
3018
+ table {
3019
+ border-collapse: collapse;
3020
+ width: 100%;
3021
+ }
3022
+ th {
3023
+ background: #f1f5f9;
3024
+ padding: 12px;
3025
+ text-align: left;
3026
+ font-weight: 600;
3027
+ border-bottom: 2px solid #e2e8f0;
3028
+ }
3029
+ td {
3030
+ padding: 12px;
3031
+ border-bottom: 1px solid #e2e8f0;
3032
+ }
3033
+
3034
+ /* Dark mode adjustments */
3035
+ .dark .stage-header {
3036
+ background: linear-gradient(90deg, #1e293b 0%, #0f172a 100%);
3037
+ border-left-color: #3b82f6;
3038
+ }
3039
+ .dark .stage-header h2 {
3040
+ color: #f1f5f9;
3041
+ }
3042
+ .dark .benchmark-card {
3043
+ background: #1e293b;
3044
+ border-color: #334155;
3045
+ }
3046
+ .dark .action-item {
3047
+ background: #1e293b;
3048
+ border-color: #334155;
3049
+ }
3050
+ """
3051
 
3052
  with gr.Blocks(
3053
  title="Design System Extractor v2",
3054
+ theme=corporate_theme,
3055
+ css=custom_css
 
 
3056
  ) as app:
3057
 
3058
+ # Header with branding
3059
+ gr.HTML("""
3060
+ <div class="app-header">
3061
+ <h1>🎨 Design System Extractor v2</h1>
3062
+ <p>Reverse-engineer design systems from live websites β€’ AI-powered analysis β€’ Figma-ready export</p>
3063
+ </div>
 
 
3064
  """)
3065
 
3066
  # =================================================================
 
3221
  # STAGE 2: AI UPGRADES
3222
  # =================================================================
3223
 
3224
+ with gr.Accordion("🧠 Stage 2: AI-Powered Analysis", open=False) as stage2_accordion:
3225
+
3226
+ # Stage header
3227
+ gr.HTML("""
3228
+ <div class="stage-header">
3229
+ <h2>🧠 Stage 2: Multi-Agent Analysis</h2>
3230
+ <p style="color: #64748b; margin-top: 4px;">Rule Engine + Benchmark Research + LLM Agents</p>
3231
+ </div>
3232
+ """)
3233
 
3234
  stage2_status = gr.Markdown("Click 'Analyze' to start AI-powered design system analysis.")
3235
 
3236
  # =============================================================
3237
+ # NEW ARCHITECTURE CONFIGURATION
3238
  # =============================================================
3239
+ with gr.Accordion("βš™οΈ Analysis Configuration", open=True):
3240
+
3241
+ # Architecture explanation
3242
  gr.Markdown("""
3243
+ ### πŸ—οΈ New Analysis Architecture
3244
 
3245
+ | Layer | Type | What It Does | Cost |
3246
+ |-------|------|--------------|------|
3247
+ | **Layer 1** | Rule Engine | Type scale, AA check, spacing grid, color stats | FREE |
3248
+ | **Layer 2** | Benchmark Research | Fetch live specs via Firecrawl (24h cache) | ~$0.001 |
3249
+ | **Layer 3** | LLM Agents | Brand ID, Benchmark Advisor, Best Practices | ~$0.002 |
3250
+ | **Layer 4** | HEAD Synthesizer | Combine all β†’ Final recommendations | ~$0.001 |
3251
 
3252
+ **Total Cost:** ~$0.003-0.004 per analysis
3253
  """)
3254
 
3255
+ gr.Markdown("---")
3256
+
3257
+ # Benchmark selection
3258
+ gr.Markdown("### πŸ“Š Select Design Systems to Compare Against")
3259
+ gr.Markdown("*Choose which design systems to benchmark your tokens against:*")
3260
+
3261
+ benchmark_checkboxes = gr.CheckboxGroup(
3262
+ choices=[
3263
+ ("🟒 Material Design 3 (Google)", "material_design_3"),
3264
+ ("🍎 Apple HIG", "apple_hig"),
3265
+ ("πŸ›’ Shopify Polaris", "shopify_polaris"),
3266
+ ("πŸ”΅ Atlassian Design System", "atlassian_design"),
3267
+ ("πŸ”· IBM Carbon", "ibm_carbon"),
3268
+ ("🌊 Tailwind CSS", "tailwind_css"),
3269
+ ("🐜 Ant Design", "ant_design"),
3270
+ ("⚑ Chakra UI", "chakra_ui"),
3271
+ ],
3272
+ value=["material_design_3", "shopify_polaris", "atlassian_design"],
3273
+ label="Benchmarks",
3274
+ )
3275
+
3276
+ gr.Markdown("""
3277
+ <small style="color: #64748b;">
3278
+ πŸ’‘ <b>Tip:</b> Select 2-4 benchmarks for best results. More benchmarks = longer analysis time.
3279
+ <br>
3280
+ πŸ“¦ Results are cached for 24 hours to speed up subsequent analyses.
3281
+ </small>
3282
+ """)
3283
+
3284
+ # Analyze button
3285
+ with gr.Row():
3286
+ analyze_btn_v2 = gr.Button(
3287
+ "πŸš€ Run Analysis (New Architecture)",
3288
+ variant="primary",
3289
+ size="lg",
3290
+ scale=2
3291
+ )
3292
+ analyze_btn_legacy = gr.Button(
3293
+ "πŸ€– Legacy Analysis",
3294
+ variant="secondary",
3295
+ size="lg",
3296
+ scale=1
3297
+ )
3298
+
3299
+ # =============================================================
3300
+ # ANALYSIS LOG
3301
+ # =============================================================
3302
+ with gr.Accordion("πŸ“‹ Analysis Log", open=True):
3303
+ stage2_log = gr.Textbox(
3304
+ label="Log",
3305
+ lines=20,
3306
+ interactive=False,
3307
+ elem_classes=["log-container"]
3308
  )
 
3309
 
3310
+ # =============================================================
3311
+ # SCORES DASHBOARD
3312
+ # =============================================================
3313
+ gr.Markdown("---")
3314
+ gr.Markdown("## πŸ“Š Analysis Results")
3315
+
3316
+ scores_dashboard = gr.HTML(
3317
+ value="<div style='padding: 40px; background: #f8fafc; border-radius: 12px; text-align: center; color: #94a3b8;'>Scores will appear after analysis...</div>",
3318
+ label="Scores"
3319
+ )
3320
 
3321
+ # =============================================================
3322
+ # PRIORITY ACTIONS
3323
+ # =============================================================
3324
+ priority_actions_html = gr.HTML(
3325
+ value="<div style='padding: 20px; background: #f8fafc; border-radius: 8px; color: #94a3b8;'>Priority actions will appear after analysis...</div>",
3326
+ label="Priority Actions"
3327
+ )
3328
 
3329
  # =============================================================
3330
+ # BENCHMARK COMPARISON
3331
  # =============================================================
3332
  gr.Markdown("---")
3333
+ benchmark_comparison_md = gr.Markdown("*Benchmark comparison will appear after analysis*")
3334
 
3335
  # =============================================================
3336
+ # COLOR RECOMMENDATIONS
3337
  # =============================================================
3338
  gr.Markdown("---")
3339
+ gr.Markdown("## 🎨 Color Recommendations")
3340
+ gr.Markdown("*Accept or reject AI-suggested color changes:*")
3341
+
3342
+ color_recommendations_table = gr.Dataframe(
3343
+ headers=["Accept", "Role", "Current", "Issue", "Suggested", "New Contrast"],
3344
+ datatype=["bool", "str", "str", "str", "str", "str"],
3345
+ label="Color Recommendations",
3346
+ interactive=True,
3347
+ row_count=(0, "dynamic"),
3348
+ )
3349
 
3350
  # =============================================================
3351
+ # TYPOGRAPHY SECTION
3352
  # =============================================================
3353
  gr.Markdown("---")
3354
  gr.Markdown("## πŸ“ Typography")
3355
 
 
3356
  with gr.Accordion("πŸ‘οΈ Typography Visual Preview", open=True):
3357
  stage2_typography_preview = gr.HTML(
3358
  value="<div style='padding: 20px; background: #f5f5f5; border-radius: 8px; color: #666;'>Typography preview will appear after analysis...</div>",
 
3546
  outputs=[colors_table, typography_table, spacing_table],
3547
  )
3548
 
3549
+ # Stage 2: NEW Architecture Analyze
3550
+ analyze_btn_v2.click(
3551
+ fn=run_stage2_analysis_v2,
3552
+ inputs=[benchmark_checkboxes],
3553
+ outputs=[
3554
+ stage2_status,
3555
+ stage2_log,
3556
+ benchmark_comparison_md,
3557
+ scores_dashboard,
3558
+ priority_actions_html,
3559
+ color_recommendations_table,
3560
+ typography_desktop,
3561
+ typography_mobile,
3562
+ stage2_typography_preview,
3563
+ ],
3564
+ )
3565
+
3566
+ # Stage 2: Legacy Analyze (keep for backward compatibility)
3567
+ analyze_btn_legacy.click(
3568
  fn=run_stage2_analysis,
3569
+ inputs=[],
3570
+ outputs=[stage2_status, stage2_log, benchmark_comparison_md, scores_dashboard,
3571
  typography_desktop, typography_mobile, spacing_comparison,
3572
  base_colors_display, color_ramps_display, radius_display, shadows_display,
3573
  stage2_typography_preview, stage2_color_ramps_preview,
 
3599
 
3600
  gr.Markdown("""
3601
  ---
3602
+ **Design System Extractor v2** | Built with Playwright + Firecrawl + LangGraph + HuggingFace
3603
 
3604
  *A semi-automated co-pilot for design system recovery and modernization.*
3605
+
3606
+ **New Architecture:** Rule Engine (FREE) + Benchmark Research (Firecrawl) + LLM Agents
3607
  """)
3608
 
3609
  return app