Upload app.py
Browse files
app.py
CHANGED
|
@@ -709,7 +709,11 @@ async def run_stage2_analysis(competitors_str: str = "", progress=gr.Progress())
|
|
| 709 |
state.log("")
|
| 710 |
state.log("π¨ Generating visual previews...")
|
| 711 |
|
| 712 |
-
from core.preview_generator import
|
|
|
|
|
|
|
|
|
|
|
|
|
| 713 |
|
| 714 |
primary_font = fonts.get("primary", "Open Sans")
|
| 715 |
|
|
@@ -737,24 +741,54 @@ async def run_stage2_analysis(competitors_str: str = "", progress=gr.Progress())
|
|
| 737 |
sample_text="The quick brown fox jumps over the lazy dog",
|
| 738 |
)
|
| 739 |
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 743 |
|
| 744 |
-
state.log(" β
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 745 |
|
| 746 |
progress(1.0, desc="β
Analysis complete!")
|
| 747 |
|
| 748 |
return (status, state.get_logs(), brand_md, font_families_md,
|
| 749 |
typography_desktop_data, typography_mobile_data, spacing_data,
|
| 750 |
base_colors_md, color_ramps_md, radius_md, shadows_md,
|
| 751 |
-
typography_preview_html, color_ramps_preview_html
|
|
|
|
| 752 |
|
| 753 |
except Exception as e:
|
| 754 |
import traceback
|
| 755 |
state.log(f"β Error: {str(e)}")
|
| 756 |
state.log(traceback.format_exc())
|
| 757 |
-
return (f"β Analysis failed: {str(e)}", state.get_logs(), "", "", None, None, None, "", "", "", "", "", "")
|
| 758 |
|
| 759 |
|
| 760 |
def normalized_to_dict(normalized) -> dict:
|
|
@@ -1052,6 +1086,303 @@ def format_font_families_display(fonts: dict) -> str:
|
|
| 1052 |
return "\n".join(lines)
|
| 1053 |
|
| 1054 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1055 |
def format_typography_comparison_viewport(normalized_tokens, base_size: int, viewport: str) -> list:
|
| 1056 |
"""Format typography comparison for a specific viewport."""
|
| 1057 |
if not normalized_tokens:
|
|
@@ -1304,8 +1635,8 @@ def snap_to_grid(value: float, base: int) -> int:
|
|
| 1304 |
return round(value / base) * base
|
| 1305 |
|
| 1306 |
|
| 1307 |
-
def apply_selected_upgrades(type_choice: str, spacing_choice: str, apply_ramps: bool):
|
| 1308 |
-
"""Apply selected upgrade options."""
|
| 1309 |
if not state.upgrade_recommendations:
|
| 1310 |
return "β Run analysis first", ""
|
| 1311 |
|
|
@@ -1322,6 +1653,39 @@ def apply_selected_upgrades(type_choice: str, spacing_choice: str, apply_ramps:
|
|
| 1322 |
state.log(f" Spacing: {spacing_choice}")
|
| 1323 |
state.log(f" Color Ramps: {'Yes' if apply_ramps else 'No'}")
|
| 1324 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1325 |
state.log("β
Upgrades applied! Proceed to Stage 3 for export.")
|
| 1326 |
|
| 1327 |
return "β
Upgrades applied! Proceed to Stage 3 to export.", state.get_logs()
|
|
@@ -2021,13 +2385,33 @@ def create_ui():
|
|
| 2021 |
gr.Markdown("*Font family will be preserved. Sizes rounded to even numbers.*")
|
| 2022 |
|
| 2023 |
# =============================================================
|
| 2024 |
-
# COLORS SECTION - Base Colors + Ramps
|
| 2025 |
# =============================================================
|
| 2026 |
gr.Markdown("---")
|
| 2027 |
gr.Markdown("## π¨ Colors")
|
| 2028 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2029 |
# Visual Preview
|
| 2030 |
-
with gr.Accordion("ποΈ Color Ramps Visual Preview", open=True):
|
| 2031 |
stage2_color_ramps_preview = gr.HTML(
|
| 2032 |
value="<div style='padding: 20px; background: #f5f5f5; border-radius: 8px; color: #666;'>Color ramps preview will appear after analysis...</div>",
|
| 2033 |
label="Color Ramps Preview"
|
|
@@ -2164,13 +2548,14 @@ def create_ui():
|
|
| 2164 |
outputs=[stage2_status, stage2_log, brand_comparison, font_families_display,
|
| 2165 |
typography_desktop, typography_mobile, spacing_comparison,
|
| 2166 |
base_colors_display, color_ramps_display, radius_display, shadows_display,
|
| 2167 |
-
stage2_typography_preview, stage2_color_ramps_preview
|
|
|
|
| 2168 |
)
|
| 2169 |
|
| 2170 |
# Stage 2: Apply upgrades
|
| 2171 |
apply_upgrades_btn.click(
|
| 2172 |
fn=apply_selected_upgrades,
|
| 2173 |
-
inputs=[type_scale_radio, spacing_radio, color_ramps_checkbox],
|
| 2174 |
outputs=[apply_status, stage2_log],
|
| 2175 |
)
|
| 2176 |
|
|
|
|
| 709 |
state.log("")
|
| 710 |
state.log("π¨ Generating visual previews...")
|
| 711 |
|
| 712 |
+
from core.preview_generator import (
|
| 713 |
+
generate_typography_preview_html,
|
| 714 |
+
generate_color_ramps_preview_html,
|
| 715 |
+
generate_semantic_color_ramps_html
|
| 716 |
+
)
|
| 717 |
|
| 718 |
primary_font = fonts.get("primary", "Open Sans")
|
| 719 |
|
|
|
|
| 741 |
sample_text="The quick brown fox jumps over the lazy dog",
|
| 742 |
)
|
| 743 |
|
| 744 |
+
# Use semantic color ramps if available, otherwise fallback to regular
|
| 745 |
+
semantic_analysis = getattr(state, 'semantic_analysis', None)
|
| 746 |
+
if semantic_analysis:
|
| 747 |
+
# Extract LLM color recommendations
|
| 748 |
+
llm_color_recs = {}
|
| 749 |
+
if final_recs and isinstance(final_recs, dict):
|
| 750 |
+
llm_color_recs = final_recs.get("color_recommendations", {})
|
| 751 |
+
# Also add accessibility fixes
|
| 752 |
+
aa_fixes = final_recs.get("accessibility_fixes", [])
|
| 753 |
+
if aa_fixes:
|
| 754 |
+
llm_color_recs["changes_made"] = [
|
| 755 |
+
f"AA fix suggested for {f.get('color', '?')}"
|
| 756 |
+
for f in aa_fixes if isinstance(f, dict)
|
| 757 |
+
][:5]
|
| 758 |
+
|
| 759 |
+
color_ramps_preview_html = generate_semantic_color_ramps_html(
|
| 760 |
+
semantic_analysis=semantic_analysis,
|
| 761 |
+
color_tokens=color_dict,
|
| 762 |
+
llm_recommendations={"color_recommendations": llm_color_recs} if llm_color_recs else None,
|
| 763 |
+
)
|
| 764 |
+
state.log(" β
Semantic color ramps preview generated (with LLM recommendations)")
|
| 765 |
+
else:
|
| 766 |
+
color_ramps_preview_html = generate_color_ramps_preview_html(
|
| 767 |
+
color_tokens=color_dict,
|
| 768 |
+
)
|
| 769 |
+
state.log(" β
Color ramps preview generated (no semantic data)")
|
| 770 |
|
| 771 |
+
state.log(" β
Typography preview generated")
|
| 772 |
+
|
| 773 |
+
# Generate LLM recommendations display
|
| 774 |
+
llm_recs_html = format_llm_color_recommendations_html(final_recs, semantic_analysis)
|
| 775 |
+
llm_recs_table = format_llm_color_recommendations_table(final_recs, semantic_analysis)
|
| 776 |
+
|
| 777 |
+
state.log(" β
LLM recommendations formatted")
|
| 778 |
|
| 779 |
progress(1.0, desc="β
Analysis complete!")
|
| 780 |
|
| 781 |
return (status, state.get_logs(), brand_md, font_families_md,
|
| 782 |
typography_desktop_data, typography_mobile_data, spacing_data,
|
| 783 |
base_colors_md, color_ramps_md, radius_md, shadows_md,
|
| 784 |
+
typography_preview_html, color_ramps_preview_html,
|
| 785 |
+
llm_recs_html, llm_recs_table)
|
| 786 |
|
| 787 |
except Exception as e:
|
| 788 |
import traceback
|
| 789 |
state.log(f"β Error: {str(e)}")
|
| 790 |
state.log(traceback.format_exc())
|
| 791 |
+
return (f"β Analysis failed: {str(e)}", state.get_logs(), "", "", None, None, None, "", "", "", "", "", "", "", [])
|
| 792 |
|
| 793 |
|
| 794 |
def normalized_to_dict(normalized) -> dict:
|
|
|
|
| 1086 |
return "\n".join(lines)
|
| 1087 |
|
| 1088 |
|
| 1089 |
+
def format_llm_color_recommendations_html(final_recs: dict, semantic_analysis: dict) -> str:
|
| 1090 |
+
"""Generate HTML showing LLM color recommendations with before/after comparison."""
|
| 1091 |
+
|
| 1092 |
+
if not final_recs:
|
| 1093 |
+
return '''
|
| 1094 |
+
<div style="padding: 20px; background: #f0f0f0 !important; border-radius: 8px; text-align: center;">
|
| 1095 |
+
<p style="color: #666 !important;">No LLM recommendations available yet. Run analysis first.</p>
|
| 1096 |
+
</div>
|
| 1097 |
+
'''
|
| 1098 |
+
|
| 1099 |
+
color_recs = final_recs.get("color_recommendations", {})
|
| 1100 |
+
aa_fixes = final_recs.get("accessibility_fixes", [])
|
| 1101 |
+
|
| 1102 |
+
if not color_recs and not aa_fixes:
|
| 1103 |
+
return '''
|
| 1104 |
+
<div style="padding: 20px; background: #d4edda !important; border-radius: 8px; border: 1px solid #28a745;">
|
| 1105 |
+
<p style="color: #155724 !important; margin: 0;">β
No color changes recommended. Your colors look good!</p>
|
| 1106 |
+
</div>
|
| 1107 |
+
'''
|
| 1108 |
+
|
| 1109 |
+
# Build recommendations HTML
|
| 1110 |
+
recs_html = ""
|
| 1111 |
+
|
| 1112 |
+
# Process color recommendations
|
| 1113 |
+
for role, rec in color_recs.items():
|
| 1114 |
+
if not isinstance(rec, dict):
|
| 1115 |
+
continue
|
| 1116 |
+
if role in ["generate_ramps_for", "changes_made"]:
|
| 1117 |
+
continue
|
| 1118 |
+
|
| 1119 |
+
current = rec.get("current", "?")
|
| 1120 |
+
suggested = rec.get("suggested", current)
|
| 1121 |
+
action = rec.get("action", "keep")
|
| 1122 |
+
rationale = rec.get("rationale", "")
|
| 1123 |
+
|
| 1124 |
+
if action == "keep" or suggested == current:
|
| 1125 |
+
# No change needed
|
| 1126 |
+
recs_html += f'''
|
| 1127 |
+
<div class="llm-rec-row keep">
|
| 1128 |
+
<div class="rec-color-box" style="background: {current};"></div>
|
| 1129 |
+
<div class="rec-details">
|
| 1130 |
+
<span class="rec-role">{role}</span>
|
| 1131 |
+
<span class="rec-current">{current}</span>
|
| 1132 |
+
<span class="rec-action keep">β Keep</span>
|
| 1133 |
+
</div>
|
| 1134 |
+
</div>
|
| 1135 |
+
'''
|
| 1136 |
+
else:
|
| 1137 |
+
# Change suggested
|
| 1138 |
+
recs_html += f'''
|
| 1139 |
+
<div class="llm-rec-row change">
|
| 1140 |
+
<div class="rec-comparison">
|
| 1141 |
+
<div class="rec-before">
|
| 1142 |
+
<div class="rec-color-box" style="background: {current};"></div>
|
| 1143 |
+
<span class="rec-label">Before</span>
|
| 1144 |
+
<span class="rec-hex">{current}</span>
|
| 1145 |
+
</div>
|
| 1146 |
+
<span class="rec-arrow">β</span>
|
| 1147 |
+
<div class="rec-after">
|
| 1148 |
+
<div class="rec-color-box" style="background: {suggested};"></div>
|
| 1149 |
+
<span class="rec-label">After</span>
|
| 1150 |
+
<span class="rec-hex">{suggested}</span>
|
| 1151 |
+
</div>
|
| 1152 |
+
</div>
|
| 1153 |
+
<div class="rec-details">
|
| 1154 |
+
<span class="rec-role">{role}</span>
|
| 1155 |
+
<span class="rec-rationale">{rationale[:80]}...</span>
|
| 1156 |
+
</div>
|
| 1157 |
+
</div>
|
| 1158 |
+
'''
|
| 1159 |
+
|
| 1160 |
+
# Process accessibility fixes
|
| 1161 |
+
for fix in aa_fixes:
|
| 1162 |
+
if not isinstance(fix, dict):
|
| 1163 |
+
continue
|
| 1164 |
+
|
| 1165 |
+
color = fix.get("color", "?")
|
| 1166 |
+
role = fix.get("role", "unknown")
|
| 1167 |
+
issue = fix.get("issue", "contrast issue")
|
| 1168 |
+
fix_color = fix.get("fix", color)
|
| 1169 |
+
current_contrast = fix.get("current_contrast", "?")
|
| 1170 |
+
fixed_contrast = fix.get("fixed_contrast", "?")
|
| 1171 |
+
|
| 1172 |
+
if fix_color and fix_color != color:
|
| 1173 |
+
recs_html += f'''
|
| 1174 |
+
<div class="llm-rec-row aa-fix">
|
| 1175 |
+
<div class="rec-comparison">
|
| 1176 |
+
<div class="rec-before">
|
| 1177 |
+
<div class="rec-color-box" style="background: {color};"></div>
|
| 1178 |
+
<span class="rec-label">β οΈ {current_contrast}:1</span>
|
| 1179 |
+
<span class="rec-hex">{color}</span>
|
| 1180 |
+
</div>
|
| 1181 |
+
<span class="rec-arrow">β</span>
|
| 1182 |
+
<div class="rec-after">
|
| 1183 |
+
<div class="rec-color-box" style="background: {fix_color};"></div>
|
| 1184 |
+
<span class="rec-label">β {fixed_contrast}:1</span>
|
| 1185 |
+
<span class="rec-hex">{fix_color}</span>
|
| 1186 |
+
</div>
|
| 1187 |
+
</div>
|
| 1188 |
+
<div class="rec-details">
|
| 1189 |
+
<span class="rec-role">{role}</span>
|
| 1190 |
+
<span class="rec-issue">π΄ {issue}</span>
|
| 1191 |
+
</div>
|
| 1192 |
+
</div>
|
| 1193 |
+
'''
|
| 1194 |
+
|
| 1195 |
+
if not recs_html:
|
| 1196 |
+
return '''
|
| 1197 |
+
<div style="padding: 20px; background: #d4edda !important; border-radius: 8px; border: 1px solid #28a745;">
|
| 1198 |
+
<p style="color: #155724 !important; margin: 0;">β
No color changes recommended. Your colors look good!</p>
|
| 1199 |
+
</div>
|
| 1200 |
+
'''
|
| 1201 |
+
|
| 1202 |
+
html = f'''
|
| 1203 |
+
<style>
|
| 1204 |
+
.llm-recs-container {{
|
| 1205 |
+
font-family: system-ui, -apple-system, sans-serif;
|
| 1206 |
+
background: #f5f5f5 !important;
|
| 1207 |
+
border-radius: 12px;
|
| 1208 |
+
padding: 16px;
|
| 1209 |
+
}}
|
| 1210 |
+
|
| 1211 |
+
.llm-rec-row {{
|
| 1212 |
+
display: flex;
|
| 1213 |
+
align-items: center;
|
| 1214 |
+
padding: 12px;
|
| 1215 |
+
margin-bottom: 12px;
|
| 1216 |
+
border-radius: 8px;
|
| 1217 |
+
background: #ffffff !important;
|
| 1218 |
+
border: 1px solid #e0e0e0 !important;
|
| 1219 |
+
}}
|
| 1220 |
+
|
| 1221 |
+
.llm-rec-row.change {{
|
| 1222 |
+
border-left: 4px solid #f59e0b !important;
|
| 1223 |
+
}}
|
| 1224 |
+
|
| 1225 |
+
.llm-rec-row.aa-fix {{
|
| 1226 |
+
border-left: 4px solid #dc2626 !important;
|
| 1227 |
+
background: #fef2f2 !important;
|
| 1228 |
+
}}
|
| 1229 |
+
|
| 1230 |
+
.llm-rec-row.keep {{
|
| 1231 |
+
border-left: 4px solid #22c55e !important;
|
| 1232 |
+
background: #f0fdf4 !important;
|
| 1233 |
+
}}
|
| 1234 |
+
|
| 1235 |
+
.rec-comparison {{
|
| 1236 |
+
display: flex;
|
| 1237 |
+
align-items: center;
|
| 1238 |
+
gap: 12px;
|
| 1239 |
+
margin-right: 20px;
|
| 1240 |
+
}}
|
| 1241 |
+
|
| 1242 |
+
.rec-before, .rec-after {{
|
| 1243 |
+
display: flex;
|
| 1244 |
+
flex-direction: column;
|
| 1245 |
+
align-items: center;
|
| 1246 |
+
gap: 4px;
|
| 1247 |
+
}}
|
| 1248 |
+
|
| 1249 |
+
.rec-color-box {{
|
| 1250 |
+
width: 48px;
|
| 1251 |
+
height: 48px;
|
| 1252 |
+
border-radius: 8px;
|
| 1253 |
+
border: 2px solid rgba(0,0,0,0.15) !important;
|
| 1254 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
| 1255 |
+
}}
|
| 1256 |
+
|
| 1257 |
+
.rec-label {{
|
| 1258 |
+
font-size: 11px;
|
| 1259 |
+
font-weight: 600;
|
| 1260 |
+
color: #666 !important;
|
| 1261 |
+
}}
|
| 1262 |
+
|
| 1263 |
+
.rec-hex {{
|
| 1264 |
+
font-family: 'SF Mono', Monaco, monospace;
|
| 1265 |
+
font-size: 11px;
|
| 1266 |
+
color: #333 !important;
|
| 1267 |
+
}}
|
| 1268 |
+
|
| 1269 |
+
.rec-arrow {{
|
| 1270 |
+
font-size: 20px;
|
| 1271 |
+
color: #666 !important;
|
| 1272 |
+
font-weight: bold;
|
| 1273 |
+
}}
|
| 1274 |
+
|
| 1275 |
+
.rec-details {{
|
| 1276 |
+
flex: 1;
|
| 1277 |
+
display: flex;
|
| 1278 |
+
flex-direction: column;
|
| 1279 |
+
gap: 4px;
|
| 1280 |
+
}}
|
| 1281 |
+
|
| 1282 |
+
.rec-role {{
|
| 1283 |
+
font-weight: 700;
|
| 1284 |
+
font-size: 14px;
|
| 1285 |
+
color: #1a1a1a !important;
|
| 1286 |
+
}}
|
| 1287 |
+
|
| 1288 |
+
.rec-action {{
|
| 1289 |
+
font-size: 12px;
|
| 1290 |
+
padding: 2px 8px;
|
| 1291 |
+
border-radius: 4px;
|
| 1292 |
+
}}
|
| 1293 |
+
|
| 1294 |
+
.rec-action.keep {{
|
| 1295 |
+
background: #dcfce7 !important;
|
| 1296 |
+
color: #166534 !important;
|
| 1297 |
+
}}
|
| 1298 |
+
|
| 1299 |
+
.rec-rationale {{
|
| 1300 |
+
font-size: 12px;
|
| 1301 |
+
color: #666 !important;
|
| 1302 |
+
}}
|
| 1303 |
+
|
| 1304 |
+
.rec-issue {{
|
| 1305 |
+
font-size: 12px;
|
| 1306 |
+
color: #991b1b !important;
|
| 1307 |
+
font-weight: 500;
|
| 1308 |
+
}}
|
| 1309 |
+
</style>
|
| 1310 |
+
|
| 1311 |
+
<div class="llm-recs-container">
|
| 1312 |
+
{recs_html}
|
| 1313 |
+
</div>
|
| 1314 |
+
'''
|
| 1315 |
+
|
| 1316 |
+
return html
|
| 1317 |
+
|
| 1318 |
+
|
| 1319 |
+
def format_llm_color_recommendations_table(final_recs: dict, semantic_analysis: dict) -> list:
|
| 1320 |
+
"""Generate table data for LLM color recommendations with accept/reject checkboxes."""
|
| 1321 |
+
|
| 1322 |
+
rows = []
|
| 1323 |
+
|
| 1324 |
+
if not final_recs:
|
| 1325 |
+
return rows
|
| 1326 |
+
|
| 1327 |
+
color_recs = final_recs.get("color_recommendations", {})
|
| 1328 |
+
aa_fixes = final_recs.get("accessibility_fixes", [])
|
| 1329 |
+
|
| 1330 |
+
# Process color recommendations
|
| 1331 |
+
for role, rec in color_recs.items():
|
| 1332 |
+
if not isinstance(rec, dict):
|
| 1333 |
+
continue
|
| 1334 |
+
if role in ["generate_ramps_for", "changes_made"]:
|
| 1335 |
+
continue
|
| 1336 |
+
|
| 1337 |
+
current = rec.get("current", "?")
|
| 1338 |
+
suggested = rec.get("suggested", current)
|
| 1339 |
+
action = rec.get("action", "keep")
|
| 1340 |
+
rationale = rec.get("rationale", "")[:50]
|
| 1341 |
+
|
| 1342 |
+
if action != "keep" and suggested != current:
|
| 1343 |
+
# Calculate contrast improvement
|
| 1344 |
+
try:
|
| 1345 |
+
from core.color_utils import get_contrast_with_white
|
| 1346 |
+
old_contrast = get_contrast_with_white(current)
|
| 1347 |
+
new_contrast = get_contrast_with_white(suggested)
|
| 1348 |
+
contrast_str = f"{old_contrast:.1f} β {new_contrast:.1f}"
|
| 1349 |
+
except:
|
| 1350 |
+
contrast_str = "?"
|
| 1351 |
+
|
| 1352 |
+
rows.append([
|
| 1353 |
+
True, # Accept checkbox (default True)
|
| 1354 |
+
role,
|
| 1355 |
+
current,
|
| 1356 |
+
rationale or action,
|
| 1357 |
+
suggested,
|
| 1358 |
+
contrast_str,
|
| 1359 |
+
])
|
| 1360 |
+
|
| 1361 |
+
# Process accessibility fixes
|
| 1362 |
+
for fix in aa_fixes:
|
| 1363 |
+
if not isinstance(fix, dict):
|
| 1364 |
+
continue
|
| 1365 |
+
|
| 1366 |
+
color = fix.get("color", "?")
|
| 1367 |
+
role = fix.get("role", "unknown")
|
| 1368 |
+
issue = fix.get("issue", "contrast")[:40]
|
| 1369 |
+
fix_color = fix.get("fix", color)
|
| 1370 |
+
current_contrast = fix.get("current_contrast", "?")
|
| 1371 |
+
fixed_contrast = fix.get("fixed_contrast", "?")
|
| 1372 |
+
|
| 1373 |
+
if fix_color and fix_color != color:
|
| 1374 |
+
rows.append([
|
| 1375 |
+
True, # Accept checkbox
|
| 1376 |
+
f"{role} (AA fix)",
|
| 1377 |
+
color,
|
| 1378 |
+
issue,
|
| 1379 |
+
fix_color,
|
| 1380 |
+
f"{current_contrast}:1 β {fixed_contrast}:1",
|
| 1381 |
+
])
|
| 1382 |
+
|
| 1383 |
+
return rows
|
| 1384 |
+
|
| 1385 |
+
|
| 1386 |
def format_typography_comparison_viewport(normalized_tokens, base_size: int, viewport: str) -> list:
|
| 1387 |
"""Format typography comparison for a specific viewport."""
|
| 1388 |
if not normalized_tokens:
|
|
|
|
| 1635 |
return round(value / base) * base
|
| 1636 |
|
| 1637 |
|
| 1638 |
+
def apply_selected_upgrades(type_choice: str, spacing_choice: str, apply_ramps: bool, color_recs_table: list = None):
|
| 1639 |
+
"""Apply selected upgrade options including LLM color recommendations."""
|
| 1640 |
if not state.upgrade_recommendations:
|
| 1641 |
return "β Run analysis first", ""
|
| 1642 |
|
|
|
|
| 1653 |
state.log(f" Spacing: {spacing_choice}")
|
| 1654 |
state.log(f" Color Ramps: {'Yes' if apply_ramps else 'No'}")
|
| 1655 |
|
| 1656 |
+
# Process accepted color recommendations
|
| 1657 |
+
accepted_color_changes = []
|
| 1658 |
+
if color_recs_table:
|
| 1659 |
+
state.log("")
|
| 1660 |
+
state.log(" π¨ LLM Color Recommendations:")
|
| 1661 |
+
for row in color_recs_table:
|
| 1662 |
+
if len(row) >= 5:
|
| 1663 |
+
accept = row[0] # Boolean checkbox
|
| 1664 |
+
role = row[1] # Role name
|
| 1665 |
+
current = row[2] # Current color
|
| 1666 |
+
issue = row[3] # Issue description
|
| 1667 |
+
suggested = row[4] # Suggested color
|
| 1668 |
+
|
| 1669 |
+
if accept and suggested and current != suggested:
|
| 1670 |
+
accepted_color_changes.append({
|
| 1671 |
+
"role": role,
|
| 1672 |
+
"from": current,
|
| 1673 |
+
"to": suggested,
|
| 1674 |
+
"reason": issue,
|
| 1675 |
+
})
|
| 1676 |
+
state.log(f" ββ β
ACCEPTED: {role}")
|
| 1677 |
+
state.log(f" β ββ {current} β {suggested}")
|
| 1678 |
+
elif not accept:
|
| 1679 |
+
state.log(f" ββ β REJECTED: {role} (keeping {current})")
|
| 1680 |
+
|
| 1681 |
+
# Store accepted changes
|
| 1682 |
+
state.selected_upgrades["color_changes"] = accepted_color_changes
|
| 1683 |
+
|
| 1684 |
+
if accepted_color_changes:
|
| 1685 |
+
state.log("")
|
| 1686 |
+
state.log(f" π {len(accepted_color_changes)} color change(s) will be applied to export")
|
| 1687 |
+
|
| 1688 |
+
state.log("")
|
| 1689 |
state.log("β
Upgrades applied! Proceed to Stage 3 for export.")
|
| 1690 |
|
| 1691 |
return "β
Upgrades applied! Proceed to Stage 3 to export.", state.get_logs()
|
|
|
|
| 2385 |
gr.Markdown("*Font family will be preserved. Sizes rounded to even numbers.*")
|
| 2386 |
|
| 2387 |
# =============================================================
|
| 2388 |
+
# COLORS SECTION - Base Colors + Ramps + LLM Recommendations
|
| 2389 |
# =============================================================
|
| 2390 |
gr.Markdown("---")
|
| 2391 |
gr.Markdown("## π¨ Colors")
|
| 2392 |
|
| 2393 |
+
# LLM Recommendations Section (NEW)
|
| 2394 |
+
with gr.Accordion("π€ LLM Color Recommendations", open=True):
|
| 2395 |
+
gr.Markdown("""
|
| 2396 |
+
*The LLMs analyzed your colors and made these suggestions. Accept or reject each one.*
|
| 2397 |
+
""")
|
| 2398 |
+
|
| 2399 |
+
llm_color_recommendations = gr.HTML(
|
| 2400 |
+
value="<div style='padding: 20px; background: #f5f5f5; border-radius: 8px; color: #666;'>LLM recommendations will appear after analysis...</div>",
|
| 2401 |
+
label="LLM Recommendations"
|
| 2402 |
+
)
|
| 2403 |
+
|
| 2404 |
+
# Accept/Reject table for color recommendations
|
| 2405 |
+
color_recommendations_table = gr.Dataframe(
|
| 2406 |
+
headers=["Accept", "Role", "Current", "Issue", "Suggested", "Contrast"],
|
| 2407 |
+
datatype=["bool", "str", "str", "str", "str", "str"],
|
| 2408 |
+
label="Color Recommendations",
|
| 2409 |
+
interactive=True,
|
| 2410 |
+
col_count=(6, "fixed"),
|
| 2411 |
+
)
|
| 2412 |
+
|
| 2413 |
# Visual Preview
|
| 2414 |
+
with gr.Accordion("ποΈ Color Ramps Visual Preview (Semantic Groups)", open=True):
|
| 2415 |
stage2_color_ramps_preview = gr.HTML(
|
| 2416 |
value="<div style='padding: 20px; background: #f5f5f5; border-radius: 8px; color: #666;'>Color ramps preview will appear after analysis...</div>",
|
| 2417 |
label="Color Ramps Preview"
|
|
|
|
| 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,
|
| 2552 |
+
llm_color_recommendations, color_recommendations_table],
|
| 2553 |
)
|
| 2554 |
|
| 2555 |
# Stage 2: Apply upgrades
|
| 2556 |
apply_upgrades_btn.click(
|
| 2557 |
fn=apply_selected_upgrades,
|
| 2558 |
+
inputs=[type_scale_radio, spacing_radio, color_ramps_checkbox, color_recommendations_table],
|
| 2559 |
outputs=[apply_status, stage2_log],
|
| 2560 |
)
|
| 2561 |
|