Files
tapir/docs/specs/chat-with-transcript.md
T
mathias 137804b0b1
CI / Lint / Test / Vet (push) Successful in 11s
CI / Build & Import (push) Successful in 10s
docs: spec chat-with-transcript (ADR-027)
Per-video chat against the ADR-021 stored transcript, entered from the summary
view, born from observed demand (maintainer read real summaries, some made him
want to dig deeper). HARD constraint: stored-transcript-only — never fetches
captions, never touches the rate gate or YouTube, safe by construction. Default
model = the summary's model, user-switchable among the ADR-022 chain models
(doubles as model-comparison instrumentation). Ephemeral v1 (no persisted
history); chat-only/trust-the-model with show-source verification recorded as the
natural v2. ADR-027 to be appended to DECISIONS.md as the first commit.
2026-06-11 06:38:16 +00:00

5.4 KiB

Spec — Chat with a video's stored transcript (ADR-027)

Repo: tapir · Size: medium · Solo session. Implements ADR-027. Read CLAUDE.md, DECISIONS.md (ADR-021 transcript store, ADR-022 model chain, ADR-012 isolation, ADR-027), and docs/ui-spec.md first. TBD, conventional commits, task check green per commit, templ generate after view changes.

FIRST: append ADR-027 to DECISIONS.md

ADR-027 text is provided separately (planning thread). Insert immediately before the ## Rejected alternatives heading, as the first commit, so the decision precedes the build.

What this is

A per-video chat letting the user ask questions against a video's already-stored transcript, entered from the summary view. Born from observed demand: the maintainer read real summaries and some made him want to dig deeper — this gives that "I want more" reaction somewhere to go, without watching the video.

HARD CONSTRAINT — stored-transcript-only (the safety property)

Chat is available ONLY for videos that already have a stored transcript (ADR-021). It must never trigger a caption fetch, never touch the rate gate, never reach YouTube. Entry being "from a summarized video" guarantees the transcript exists. If somehow invoked on a video with no stored transcript → show "transcript not available for chat", NO fetch. This is what makes the feature safe by construction; do not add an on-demand-fetch path (explicitly deferred).

1. Entry point

  • A "Dig deeper" / "Ask about this" affordance on the summary view of a summarized video (not the list cards — the detail/summary page). Quiet, consistent with the existing card-state styling.
  • Opens a chat panel/view scoped to that one video, with its stored transcript as context.

2. The chat

  • Read the stored transcript for the video (via the ADR-021 TranscriptStore, keyed by (provider, provider_video_id)). No fetch.
  • Send transcript + the user's question + minimal system framing to the chosen model via the existing LiteLLM gateway (the same client the summarizer uses — a chat is a different call, not a new integration).
  • Stream or return the answer; render in the chat panel. HTMX/no-JS ethos — match the existing app (the summarize status uses HTMX polling; chat can use a simple POST-and-render or HTMX streaming if clean).
  • Transcript truncation: reuse/respect TAPIR_MAX_TRANSCRIPT_CHARS (ADR-022) so a long transcript fits the model context. If truncated, the chat should be honest that it's working from a bounded portion (a quiet note), since answers about the tail of a long video may be incomplete.

3. Model selection (the instrumentation win)

  • Default model = the model that produced this video's summary. (Store/lookup which chain model summarized it — if not already recorded, this is a small addition; if recording it is non-trivial, default to the chain primary and note the gap.)
  • User can switch among the ADR-022 chain models (phi4-mini, gemma4-26b, mistral-small to start) via a simple selector in the chat panel. Switching re-runs against the same transcript — this is deliberate model-comparison instrumentation.
  • Respect the local-first / NDA posture: if TAPIR_CLOUD_FALLBACK_MODEL="" (cloud disabled), the external model is NOT offered in the switcher — only local models. Chat must honor the same "content stays local" guarantee as ADR-022.

4. Ephemeral (v1)

  • No persisted chat history. Conversation lives for the session/page. No new table, no migration.
  • (Multi-turn within a session is fine — keep the running messages in the request/page state — but nothing is written to the DB.)

5. Isolation

  • The transcript is shared/non-RLS (ADR-021) — fine, it's public content. But the chat is invoked by a user about a video in their feed; confirm the entry path is reachable only for the requesting user's own videos (the summary view is already RLS-scoped). Chat adds no new user-data surface (ephemeral), so there's nothing new to RLS — but the test should confirm a user can only open chat from their own summary view, not arbitrary video ids.

Tests

  • Chat on a video with a stored transcript → answer returned; assert NO caption-fetch / no YouTube call occurs (the safety property — this is the key assertion).
  • Chat invoked on a video with no stored transcript → honest "not available", NO fetch.
  • Model switch → re-runs against the same transcript with the selected model; cloud model absent from the switcher when TAPIR_CLOUD_FALLBACK_MODEL="".
  • Truncation honored for a long transcript; the bounded-context note shows.
  • Entry is reachable only from the user's own summary view (isolation).

Out of scope / deferred (record, don't build)

  • Persisted chat history (per-user, RLS-scoped) — deferred until evidence anyone revisits a conversation.
  • Show-source / transcript-verification UI — the natural v2 (ADR-027 records it); v1 is chat-only/trust-the-model. ADR-021's stored transcript makes v2 cheap when wanted.
  • On-demand fetch for un-stored videos — would reintroduce the caption-fetch surface the stored-only constraint removes. Not now.
  • Anything that nudges the user to return (ADR-020 — gate contamination).

Boundaries

Stored-transcript-only (HARD). No rate-gate/fetch surface. No auth changes. No new persisted state in v1. Reuse the existing gateway client + truncation config; don't build a new model integration.