"""HTML for the results list. The list is plain markup with `data-` attributes; a single delegated click handler in the page head turns any timestamp into a seek. No per-card JavaScript, no component churn — Gradio only ever hands back a string. """ from __future__ import annotations from html import escape from search import MAX_SHOWN_EVENTS, Hit def _safe_url(url: str | None) -> str: """Only ever emit an http(s) link. The URLs come from the dataset, and a link labelled 'archive.org' should not be able to send a visitor to a `javascript:` or `data:` target if a row is ever wrong.""" url = (url or "").strip() return url if url.startswith(("https://", "http://")) else "#" def clock(seconds: float) -> str: seconds = max(0, int(round(seconds))) h, rem = divmod(seconds, 3600) m, s = divmod(rem, 60) return f"{h}:{m:02d}:{s:02d}" if h else f"{m}:{s:02d}" MIN_EVENTS = 3 def _events_html(hit: Hit) -> str: """Matched events, padded with their neighbours up to a readable minimum. A semantic hit often matches no event text literally — the query never appears in the captions — so without padding those results collapse to one line. Padding is greyed: only literal matches get the accent, which keeps the colour honest about what it means. """ matched = hit.matched[:MAX_SHOWN_EVENTS] hits = {id(e) for e in matched} shown = list(matched) for event in hit.events: if len(shown) >= MIN_EVENTS: break if id(event) not in hits: shown.append(event) shown.sort(key=lambda e: e["start"]) if not shown: return "" rows = [] for event in shown: rows.append( "" '' "{text}" "".format( cls=' class="hit"' if id(event) in hits else "", src=escape(hit.video_url, quote=True), t=f"{event['start']:.2f}", title=escape(hit.title, quote=True), when=clock(event["start"]), text=escape(event["text"]), ) ) return f'' def moment(hit: Hit) -> str: when = clock(hit.jump) stamp = " · ".join( part for part in ( str(hit.year) if hit.year else "undated", f"{clock(hit.chunk_start)}–{clock(hit.chunk_end)}", ) if part ) scene = escape(hit.scene) scene_html = ( f'
scene

{scene}

' if scene else "" ) return f"""

{escape(hit.title)}

{stamp} archive.org

{_events_html(hit)} {scene_html}
""" def opening(hits: list[Hit]) -> str: """What an empty search box shows: some of the collection, not a blank page. Every tile plays, so the first thing the page offers is the thing it does. """ if not hits: return "" tiles = [] for hit in hits: when = clock(hit.chunk_start) tiles.append( f"""

{escape(hit.title)}

{hit.year or "undated"}

""" ) return ( '

a few moments, at random — click one to read what the ' "model saw

" f'
{"".join(tiles)}
' ) def results(hits: list[Hit], query: str, elapsed_ms: float) -> str: if not query.strip(): return "" if not hits: return ( '

Nothing for %s. ' "The captions describe what is on screen — try what you would " "see: a place, an object, an action.

" % escape(query) ) films = len({h.identifier for h in hits}) head = ( f'

{len(hits)} moments · {films} ' f'{"film" if films == 1 else "films"}' f'{elapsed_ms:.0f} ms

' ) return head + "".join(moment(h) for h in hits)