File size: 6,102 Bytes
e297a6f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | """UI Components - Build HTML cards and tables with evaluation results."""
import pandas as pd
def build_product_cards(df, evaluations):
"""
Build HTML cards for products grouped by verdict.
Args:
df: DataFrame with product data
evaluations: List of evaluation dicts (same length as df)
Returns:
str: HTML string with product cards
"""
if df.empty:
return "<p>No products to display</p>"
# Add evaluation data to dataframe
df = df.copy()
df["verdict"] = [e.get("verdict", "possible") for e in evaluations]
df["notes"] = [e.get("notes", "") for e in evaluations]
df["requirements_met"] = [e.get("requirements_met", []) for e in evaluations]
# Check if there are actual requirements to display
has_requirements = any(e.get("requirements_met") for e in evaluations)
# Group by verdict
matches = df[df["verdict"] == "match"]
possibles = df[df["verdict"] == "possible"]
no_matches = df[df["verdict"] == "no_match"]
html_parts = ["<div style='font-family: system-ui, sans-serif; background: #1a1a1a; padding: 1rem; border-radius: 0.5rem;'>"]
# Show all products in a single grid - no verdict sections
# Just show the products with their confirmed features (green checkmarks)
all_products = pd.concat([matches, possibles, no_matches]) if not matches.empty or not possibles.empty or not no_matches.empty else pd.DataFrame()
if not all_products.empty:
html_parts.append("<h3 style='color: #ffffff; margin-bottom: 1rem;'>Products ({})</h3>".format(len(all_products)))
html_parts.append("<div style='display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1rem; margin-bottom: 2rem;'>")
for _, product in all_products.iterrows():
html_parts.append(_build_card(product, "match", has_requirements))
html_parts.append("</div>")
html_parts.append("</div>")
return "\n".join(html_parts)
def _build_card(product, verdict, has_requirements=True):
"""Build a single product card."""
# Dark theme - all cards get dark gray background
colors = {
"bg": "#2d2d2d", # Dark gray background
"border": "#4a4a4a", # Slightly lighter gray border
"text": "#ffffff", # White text
"price": "#93c5fd" # Light blue for prices
}
# No verdict icons - checkmarks tell the story
icon = ""
title = product.get("title", "Unknown")
price = product.get("price", 0)
source = product.get("source", "")
rating = product.get("rating", None)
reviews = product.get("reviews", 0)
link = product.get("link", "")
thumbnail = product.get("thumbnail", "")
notes = product.get("notes", "")
requirements_met = product.get("requirements_met", [])
# Build requirements checklist - ONLY show confirmed features (met)
req_html = ""
if has_requirements and requirements_met:
confirmed_features = [r for r in requirements_met if r.get("status") == "met"]
if confirmed_features:
req_html = "<div style='margin-top: 0.5rem; font-size: 0.85rem;'>"
for req_item in confirmed_features:
req_text = req_item.get("req", "")
req_html += f"<div style='color: #4ade80;'>✓ {req_text}</div>" # Light green checkmark
req_html += "</div>"
# Clickable thumbnail
thumbnail_html = ""
if thumbnail and link:
thumbnail_html = f'<a href="{link}" target="_blank"><img src="{thumbnail}" style="width: 100%; height: 200px; object-fit: contain; margin-bottom: 0.75rem; cursor: pointer; transition: opacity 0.2s;" onmouseover="this.style.opacity=\'0.8\'" onmouseout="this.style.opacity=\'1\'" /></a>'
elif thumbnail:
thumbnail_html = f'<img src="{thumbnail}" style="width: 100%; height: 200px; object-fit: contain; margin-bottom: 0.75rem;" />'
card_html = f"""
<div style='border: 2px solid {colors["border"]}; border-radius: 0.5rem; padding: 1rem; background: {colors["bg"]};'>
{thumbnail_html}
<h4 style='margin: 0 0 0.5rem 0; font-size: 0.95rem; line-height: 1.3; color: {colors["text"]};'>{title}</h4>
<div style='display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.5rem;'>
<span style='font-weight: bold; font-size: 1.25rem; color: {colors["price"]};'>${price:.2f}</span>
<span style='font-size: 0.85rem; color: #a0a0a0;'>{source}</span>
</div>
{f'<div style="margin-bottom: 0.5rem; font-size: 0.85rem; color: #d0d0d0;">⭐ {rating} ({reviews} reviews)</div>' if rating else ''}
{req_html}
</div>
"""
return card_html
def build_comparison_table(df, evaluations):
"""
Build a comparison table with verdict column.
Args:
df: DataFrame with product data
evaluations: List of evaluation dicts
Returns:
pd.DataFrame: DataFrame ready for gr.Dataframe
"""
if df.empty:
return pd.DataFrame()
display_df = df.copy()
# Add verdict and notes columns
display_df["Verdict"] = [e.get("verdict", "possible") for e in evaluations]
display_df["AI Notes"] = [e.get("notes", "") for e in evaluations]
# Select and rename columns for display (no URL column)
columns_map = {
"title": "Title",
"price": "Price ($)",
"source": "Store",
"rating": "Rating",
"reviews": "Reviews",
"Verdict": "Verdict",
"AI Notes": "AI Notes"
}
display_df = display_df[[c for c in columns_map.keys() if c in display_df.columns]]
display_df = display_df.rename(columns=columns_map)
# Sort by verdict priority (match, possible, no_match)
verdict_order = {"match": 0, "possible": 1, "no_match": 2}
display_df["_sort"] = display_df["Verdict"].map(verdict_order)
display_df = display_df.sort_values("_sort").drop("_sort", axis=1)
return display_df.reset_index(drop=True)
|