"""Gradio UI builders for the TRIBE v2 brain-score Space (Cortical Observatory).
Mirrors the qwen-image-editor shape (PLAN.md §6 "Code shape"): per-mode
``build_*_tab()`` builders each return a ``dict[str, gr.components.Component]``
so ``app.py`` can wire ``.click()`` / ``.change()`` handlers without reaching
into local scopes. **No event wiring lives here** -- app.py owns it.
The three modes (PLAN.md §2) all feed the *same* metric-timeline pipeline, so
each tab differs only in its input widget (+ an ``audio_only`` debug toggle on
the two media tabs). The shared right-hand readout is built once by
:func:`build_results`.
Help text flows through Gradio's native ``info=`` parameter (a dim subtitle
under each label). Components that don't support ``info=`` (``gr.Video``,
``gr.HTML``) carry their guidance in the label or adjacent markdown instead.
"""
from __future__ import annotations
import gradio as gr
# ---------------------------------------------------------------------------
# Metric catalogue (PLAN.md §5). The checkbox VALUES are these exact display
# names, so app.py can pass the selection straight through to
# ``plotting.timeline_figure(..., selected=...)`` and key into the curve dict.
# ---------------------------------------------------------------------------
METRICS_DEFAULT_ON: tuple[str, ...] = (
"Attention",
"Engagement / arousal",
"Virality (proxy)",
)
METRICS_OPTIONAL: tuple[str, ...] = (
"Language / semantic load",
"Self-relevance / DMN",
)
METRIC_CHOICES: tuple[str, ...] = METRICS_DEFAULT_ON + METRICS_OPTIONAL
# One-line rationale per metric, surfaced as the checkbox group's ``info=``.
_METRIC_INFO = (
"Brain-derived curves over time. Attention (dorsal/ventral + control), "
"Engagement (sensory + STS), Virality (vmPFC/mOFC value proxy) are on by "
"default; Language and Self-relevance/DMN are optional."
)
# Accepted upload suffixes per mode (PLAN.md §2 table).
_VIDEO_SUFFIXES = [".mp4", ".mov", ".mkv", ".avi", ".webm"]
_AUDIO_SUFFIXES = [".wav", ".mp3", ".flac", ".ogg"]
# Honesty caveat (PLAN.md §5) -- shown once near the metric toggles.
_PROXY_CAVEAT = (
'
Virality is a research proxy, not '
"a guarantee. facebook/tribev2 is cortical-only (no ventral "
"striatum / NAcc), so this is the validated vmPFC/mPFC complement. "
"The model's target was per-sample z-scored, so only relative "
"temporal dynamics are interpretable — absolute scores are "
"meaningless.
"
)
def _metric_toggles() -> dict[str, gr.components.Component]:
"""Metric-selection checkboxes + the proxy caveat, shared by every tab.
Returns ``{metrics, proxy_note}``: a single ``gr.CheckboxGroup`` whose value
is the list of selected metric display names (defaults = the three ON
metrics), and the caveat HTML beneath it.
"""
with gr.Group():
gr.HTML('
Metrics
')
metrics = gr.CheckboxGroup(
choices=list(METRIC_CHOICES),
value=list(METRICS_DEFAULT_ON),
label="Brain metrics to plot",
info=_METRIC_INFO,
)
proxy_note = gr.HTML(_PROXY_CAVEAT)
return dict(metrics=metrics, proxy_note=proxy_note)
def _audio_only_toggle() -> gr.components.Component:
"""The ``audio_only`` debug switch (Video/Audio tabs only, PLAN.md §11.6).
Skips ASR (whisperx) + the gated Llama text path -> much faster, and lets
the heavy pipeline be validated on-Space before Meta approval.
"""
return gr.Checkbox(
value=True,
label="⚡ Fast mode — skip speech-to-text (recommended)",
info=(
"On (default): scores the video + audio brain response — fast and "
"reliable. Uncheck for full multimodal incl. spoken-text features "
"(much slower: downloads a large ASR stack on first use)."
),
)
def _run_button(label: str = "Score") -> gr.components.Component:
"""The primary action button (activation-hot, per the theme)."""
return gr.Button(label, variant="primary", elem_classes=["co-run"])
# ===========================================================================
# Per-mode input tabs
# ===========================================================================
def build_video_tab() -> dict[str, gr.components.Component]:
"""Build the Video mode (primary) input column.
Keys: ``video, sample_btn, metrics, proxy_note, audio_only, run_btn``.
Full multimodal path: V-JEPA2 + DINOv2 + extracted-audio W2V-BERT + Llama.
"""
with gr.Column():
gr.HTML('
')
audio = gr.Audio(
label="Audio (.wav .mp3 .flac .ogg, up to 5 min)",
sources=["upload"],
type="filepath",
)
toggles = _metric_toggles()
audio_only = _audio_only_toggle()
run_btn = _run_button("Score audio")
return dict(
audio=audio,
audio_only=audio_only,
run_btn=run_btn,
**toggles,
)
def build_text_tab() -> dict[str, gr.components.Component]:
"""Build the Text mode input column.
Keys: ``text, metrics, proxy_note, run_btn``.
Path: gTTS-synthesized speech -> W2V-BERT + Llama. (No ``audio_only`` here:
text *is* the ASR-equivalent input, so the debug toggle doesn't apply.)
"""
with gr.Column():
gr.HTML('
Text
')
text = gr.Textbox(
label="Text",
info=(
"Synthesized to speech (gTTS), then scored over the spoken "
"length. English narrative works best."
),
lines=6,
placeholder=(
"Paste a script or passage. It is read aloud and the average "
"brain's response to that narration is plotted over time."
),
)
toggles = _metric_toggles()
run_btn = _run_button("Score text")
return dict(
text=text,
run_btn=run_btn,
**toggles,
)
# ===========================================================================
# Shared right-hand readout (the signature synchronized timeline + states)
# ===========================================================================
# Empty-state markup: inviting, one-line "what this does" (PLAN.md §6 states).
_EMPTY_HTML = """
🧠
Read a clip, watch the brain
Pick a mode on the left, drop in a clip (or text), choose your metrics, and
press Score. You'll get a synchronized timeline of how the
average brain would respond — scrub the media, watch the
curves; click a spike to jump there.