shopper / ui_components.py
anly656's picture
Upload ui_components.py
e297a6f verified
"""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)