from typing import List, Dict, Tuple, Any from utils import get_confidence_color, get_confidence_bg_color # Method colors (consistent across themes) METHOD_COLORS = { "base": "#f39c12", # Orange "voyage": "#3498db", # Blue "chicory": "#9b59b6", # Purple "openai": "#2ecc71", # Green "expanded": "#e74c3c", # Red "hybrid": "#1abc9c", # Turquoise "categories": "#1abc9c", # Same as hybrid "ingredients": "#f39c12" # Same as base } # Method display names METHOD_NAMES = { "base": "Base Embeddings", "voyage": "Voyage AI Reranker", "chicory": "Chicory Parser", "openai": "OpenAI Reranker", "expanded": "Expanded Description", "hybrid": "Hybrid Matching", "categories": "Category Matches", "ingredients": "Ingredient Matches" } def parse_result_item(item): """Helper function to parse result items into display text and score""" display_text = str(item) # Default score = 0.0 # Handle tuple formats if isinstance(item, tuple): if len(item) == 2: match, score = item display_text = str(match) elif len(item) == 3: id_val, text, score = item display_text = f"{id_val}: {text}" if text else str(id_val) # Handle dictionary formats elif isinstance(item, dict): if "name" in item and "score" in item: display_text = item["name"] score = item["score"] elif "category" in item and "confidence" in item: display_text = item["category"] score = item["confidence"] elif "ingredient" in item and "relevance_score" in item: display_text = item["ingredient"] score = item["relevance_score"] # Ensure score is a float try: score = float(score) except (ValueError, TypeError): score = 0.0 return display_text, score def filter_results_by_threshold(results, confidence_threshold=0.0): """Helper function to filter results by confidence threshold""" filtered_results = [] for item in results: _, score = parse_result_item(item) if score >= confidence_threshold: filtered_results.append(item) return filtered_results def format_confidence_badge(score): """Formats the confidence score as a styled span (badge)""" confidence_percent = int(score * 100) confidence_color = get_confidence_color(score) bg_color = get_confidence_bg_color(score) # Determine text color based on background lightness for better contrast # Simple heuristic: if bg_color is light (e.g., yellow, light green), use black text, otherwise use white/light text. # This is approximate. A proper solution would involve calculating luminance. light_bgs = ["#ffffcc", "#ccffcc", "#cceeff"] # Add more light hex codes if needed text_color = "#000000" if bg_color.lower() in light_bgs else "var(--text-color)" # Default to theme text color otherwise return ( f"" f"{confidence_percent}%" ) def format_result_list_html(results, confidence_threshold=0.0): """Formats a list of results (matches) into an HTML list.""" filtered_results = filter_results_by_threshold(results, confidence_threshold) if not filtered_results: return "

No matches found above confidence threshold.

" html = "" return html def format_result_card(title, content_html): """Creates a basic card structure for a single product result.""" # Use secondary background for the card, slightly stronger border return ( f"
" f"

{title}

" # Ensure header uses theme text color f"{content_html}" f"
" ) def format_info_panel(title, text): """Formats an informational panel (e.g., for expanded description).""" # Use a slightly different background, maybe derived from primary color with transparency # Or stick to secondary background for consistency return ( f"
" f"

{title}

" # Use theme text color f"

{text}

" # Use theme text color f"
" ) def format_method_results_section(method_key, results, confidence_threshold=0.0): """Formats results for a specific method within a comparison.""" method_name = METHOD_NAMES.get(method_key, method_key.replace('_', ' ').title()) color_hex = METHOD_COLORS.get(method_key, "var(--text-color)") # Fallback to theme text color results_html = format_result_list_html(results, confidence_threshold) # Keep the method-specific color for the header border return ( f"
" # Subtle dashed border between methods f"

{method_name}

" f"{results_html}" f"
" ) # --- Main Formatting Functions Called by UI Tabs --- def format_comparison_html(product, method_results, expanded_description="", confidence_threshold=0.5): """Formats the comparison results for multiple methods for one product.""" content_html = "" if expanded_description: content_html += format_info_panel("Expanded Description", expanded_description) method_order = ["base", "voyage", "chicory", "openai"] sections_html = [] for method_key in method_order: if method_key in method_results and method_results[method_key]: sections_html.append(format_method_results_section( method_key=method_key, results=method_results.get(method_key, []), confidence_threshold=confidence_threshold )) # Join sections, remove last border if sections exist if sections_html: # Remove the border-bottom style from the last section's div last_section = sections_html[-1] if "border-bottom: 1px dashed rgba(128, 128, 128, 0.2);" in last_section: sections_html[-1] = last_section.replace("border-bottom: 1px dashed rgba(128, 128, 128, 0.2);", "") content_html += "".join(sections_html) return format_result_card(title=product, content_html=content_html) def format_reranking_results_html(results, match_type="ingredients", show_scores=True, include_explanation=False, method="voyage", confidence_threshold=0.0): """Formats results from reranking methods (Voyage, OpenAI).""" if not results: return f"

No {match_type.lower()} matches found.

" html_elements = [] for result in results: product_name = result.get("product_name", "Unknown Product") matching_items = result.get("matching_items", []) item_scores = result.get("item_scores", []) explanation = result.get("explanation", "") if include_explanation else "" if len(item_scores) != len(matching_items): item_scores = [result.get("confidence", 0.0)] * len(matching_items) formatted_matches = [] for i, item in enumerate(matching_items): if ":" in str(item) and match_type == "categories": parts = str(item).split(":", 1) id_val = parts[0].strip() text = parts[1].strip() if len(parts) > 1 else "" formatted_matches.append((id_val, text, item_scores[i])) else: formatted_matches.append((str(item), item_scores[i])) content_html = "" if explanation: content_html += format_info_panel("Expanded Description", explanation) list_html = format_result_list_html(formatted_matches, confidence_threshold) content_html += list_html # Only add card if there's content to show (explanation or non-empty list html) if explanation or "{chicory_title}" if isinstance(chicory_result, dict): ingredient = chicory_result.get("ingredient", "Not found") score = chicory_result.get("confidence", 0) badge = format_confidence_badge(score) content_html += ( f"
" # Use theme bg/border f"{ingredient}" # Use theme text color f"{badge}" f"
" ) has_content = True else: content_html += "

No Chicory results available.

" # Add the main category/ingredient results match_title = METHOD_NAMES.get(match_type, match_type.capitalize()) color_hex = METHOD_COLORS.get(match_type, "var(--text-color)") # Fallback to theme text color content_html += f"

{match_title}

" list_html = format_result_list_html(categories, confidence_threshold) content_html += list_html if "