"""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('
Video · full multimodal
') video = gr.Video( label="Video (.mp4 .mov .mkv .avi .webm, up to 5 min)", sources=["upload"], height=200, ) sample_btn = gr.Button( "Try a sample clip", variant="secondary", size="sm", elem_classes=["co-sample"], ) toggles = _metric_toggles() audio_only = _audio_only_toggle() run_btn = _run_button("Score video") return dict( video=video, sample_btn=sample_btn, audio_only=audio_only, run_btn=run_btn, **toggles, ) def build_audio_tab() -> dict[str, gr.components.Component]: """Build the Audio mode input column. Keys: ``audio, metrics, proxy_note, audio_only, run_btn``. Path: W2V-BERT + Llama (ASR word context). """ with gr.Column(): gr.HTML('
Audio
') 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.
""".strip() # The custom