""" Pairwise Display Type Renders content in a side-by-side comparison layout for pairwise annotation tasks. """ import html from typing import Dict, Any, List from .base import BaseDisplay class PairwiseDisplay(BaseDisplay): """ Display type for pairwise/comparison content. Displays multiple items side-by-side for comparison annotation tasks. The data should be a list or dict with multiple items to compare. """ name = "pairwise" required_fields = ["key"] optional_fields = { "cell_width": "50%", "show_labels": True, "labels": None, # Custom labels like ["Option A", "Option B"] "vertical_on_mobile": True, } description = "Side-by-side comparison display" supports_span_target = False def render(self, field_config: Dict[str, Any], data: Any) -> str: """ Render pairwise comparison content as HTML. Args: field_config: The field configuration data: The comparison data - should be: - List of items to compare - Dict with keys for each item Returns: HTML string for the pairwise display """ if not data: return '
No comparison data provided
' # Get display options options = self.get_display_options(field_config) cell_width = options.get("cell_width", "50%") show_labels = options.get("show_labels", True) custom_labels = options.get("labels") vertical_on_mobile = options.get("vertical_on_mobile", True) field_key = html.escape(field_config.get("key", ""), quote=True) # Normalize data to a list of items items = self._normalize_items(data) if not items: return '
No items to compare
' # Generate labels labels = self._get_labels(items, custom_labels) # Calculate cell width based on number of items if cell_width == "auto": cell_width = f"{100 / len(items)}%" # Build HTML for each cell cell_html_list = [] for i, item in enumerate(items): label = labels[i] if i < len(labels) else f"Option {i + 1}" content = self._render_item(item) label_html = "" if show_labels: escaped_label = html.escape(str(label)) label_html = f'
{escaped_label}
' cell_html = f'''
{label_html}
{content}
''' cell_html_list.append(cell_html) # Combine all cells all_cells_html = "\n".join(cell_html_list) # Container classes container_classes = ["pairwise-display-content"] if vertical_on_mobile: container_classes.append("vertical-on-mobile") return f'''
{all_cells_html}
''' def _normalize_items(self, data: Any) -> List[Any]: """ Normalize data to a list of items. Args: data: Raw comparison data Returns: List of items to compare """ if isinstance(data, list): return data elif isinstance(data, dict): # Return values in order, or use special keys if present if "left" in data and "right" in data: return [data["left"], data["right"]] if "a" in data and "b" in data: return [data["a"], data["b"]] if "1" in data and "2" in data: return [data["1"], data["2"]] # Otherwise return all values return list(data.values()) else: # Single item - wrap in list return [data] def _get_labels(self, items: List[Any], custom_labels: List[str] = None) -> List[str]: """ Generate labels for the comparison items. Args: items: The items being compared custom_labels: Custom labels if provided Returns: List of label strings """ if custom_labels and len(custom_labels) >= len(items): return custom_labels[:len(items)] # Default labels if len(items) == 2: return ["A", "B"] else: return [f"Option {i + 1}" for i in range(len(items))] def _render_item(self, item: Any) -> str: """ Render a single comparison item. Args: item: The item to render Returns: HTML string for the item """ if item is None: return 'No content' if isinstance(item, str): # Plain text - escape and preserve newlines escaped = html.escape(item) return escaped.replace('\n', '
') if isinstance(item, dict): # Check for common patterns if "text" in item: text = str(item["text"]) escaped = html.escape(text) return escaped.replace('\n', '
') if "content" in item: content = str(item["content"]) escaped = html.escape(content) return escaped.replace('\n', '
') # Render as key-value pairs parts = [] for key, value in item.items(): escaped_key = html.escape(str(key)) escaped_value = html.escape(str(value)) parts.append(f'
{escaped_key}: {escaped_value}
') return ''.join(parts) if isinstance(item, list): # Render as list items parts = ['') return ''.join(parts) # Default - convert to string return html.escape(str(item)) def get_css_classes(self, field_config: Dict[str, Any]) -> List[str]: """Get CSS classes for the container.""" classes = super().get_css_classes(field_config) return classes