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.
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user