File size: 3,520 Bytes
f55d589 | 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 | """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 = """
<div id="halide-header">
<h1>Project Halide</h1>
<p>Edge-native diagnostic engine for analog film scans</p>
</div>
"""
def defect_pills_html(label_counts: dict[str, int]) -> str:
"""Render defect counts as colored pills."""
if not label_counts:
return '<p style="color: var(--halide-slate);">No defects detected.</p>'
pills: list[str] = []
for label, count in sorted(label_counts.items(), key=lambda kv: -kv[1]):
pills.append(
f'<span class="halide-defect-pill {label}">{label}: {count}</span>'
)
return '<div>' + "".join(pills) + "</div>"
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'<div class="halide-card">{"".join(rows)}</div>'
def _stat_row(label: str, value: str) -> str:
return (
'<div class="halide-stat">'
f'<span class="halide-stat-label">{label}</span>'
f'<span>{value}</span>'
"</div>"
)
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", "<br>")
return f'<div class="halide-diagnosis">{safe}</div>'
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'<div class="halide-card" style="margin-bottom: 0.6rem;">'
f'<div class="halide-section-title" style="font-size: 0.95rem;">'
f"{film} (age {age}y, {storage})</div>"
f"{defect_pills_html(counts)}"
f'<div style="color: var(--halide-slate); font-size: 0.8rem; margin-top: 0.4rem;">'
f"defects: {total} | {seconds:.2f}s | {ts:.0f}"
f"</div></div>"
)
def render_history(entries: Iterable[dict]) -> str:
items = "".join(history_row_html(e) for e in entries)
if not items:
return '<p style="color: var(--halide-slate);">No diagnoses yet.</p>'
return items
__all__ = [
"HEADER_HTML",
"THEME_CSS",
"defect_pills_html",
"stats_html",
"diagnosis_html",
"render_history",
]
|