"""UI components. Defect list rendering and shared visual helpers.""" from __future__ import annotations from typing import Iterable from ui.theme import THEME_CSS HEADER_HTML = """

Project Halide

Edge-native diagnostic engine for analog film scans

""" def defect_pills_html(label_counts: dict[str, int]) -> str: """Render defect counts as colored pills.""" if not label_counts: return '

No defects detected.

' pills: list[str] = [] for label, count in sorted(label_counts.items(), key=lambda kv: -kv[1]): pills.append( f'{label}: {count}' ) return '
' + "".join(pills) + "
" def stats_html(result: dict) -> str: """Render a stats card with defect counts and timing.""" defects = result.get("defects", {}) or {} diagnosis = result.get("diagnosis", {}) or {} total = result.get("total_seconds", 0.0) or 0.0 vision_s = defects.get("inference_seconds", 0.0) or 0.0 reasoning_s = diagnosis.get("reasoning_seconds", 0.0) or 0.0 rows: list[str] = [] rows.append(_stat_row("Total defects", str(defects.get("defect_count", 0)))) rows.append(_stat_row("Dropped (invalid)", str(defects.get("dropped_count", 0)))) rows.append(_stat_row("Vision inference", f"{vision_s:.2f}s")) rows.append(_stat_row("Reasoning", f"{reasoning_s:.2f}s")) rows.append(_stat_row("Total", f"{total:.2f}s")) rows.append(_stat_row("Vision model", _truncate(defects.get("model_path", ""), 50))) rows.append(_stat_row("Reasoning model", _truncate(diagnosis.get("model_path", ""), 50))) return f'
{"".join(rows)}
' def _stat_row(label: str, value: str) -> str: return ( '
' f'{label}' f'{value}' "
" ) def _truncate(s: str, n: int) -> str: if len(s) <= n: return s return "..." + s[-(n - 3):] def diagnosis_html(text: str) -> str: """Wrap diagnosis text in the styled card.""" safe = (text or "(no diagnosis produced)").replace("\n", "
") return f'
{safe}
' def history_row_html(entry: dict) -> str: """Render a single row in the recent-diagnoses sidebar.""" counts = entry.get("label_counts", {}) or {} total = entry.get("defect_count", 0) or 0 film = entry.get("film_type", "Unknown") age = entry.get("film_age_years", "?") storage = entry.get("storage", "?") ts = entry.get("created_at", 0) seconds = entry.get("total_seconds", 0.0) or 0.0 return ( f'
' f'
' f"{film} (age {age}y, {storage})
" f"{defect_pills_html(counts)}" f'
' f"defects: {total} | {seconds:.2f}s | {ts:.0f}" f"
" ) def render_history(entries: Iterable[dict]) -> str: items = "".join(history_row_html(e) for e in entries) if not items: return '

No diagnoses yet.

' return items __all__ = [ "HEADER_HTML", "THEME_CSS", "defect_pills_html", "stats_html", "diagnosis_html", "render_history", ]