feat(calls): transcript export (txt/vtt) + archive search (LC-393 Phase 4) #409
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/lc-395-transcript-export-search"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
LC-393 Phase 4 - the concrete, dependency-free core (browse landed in LC-394). Fixes LC-395.
Export (txt + vtt)
GET /transcripts/{id}/export?format=txt|vttdownloads a saved transcript, gated with the same access check as the transcript page, served as an attachment (Content-Disposition). Download links added to the transcript page.HH:MM:SS Speaker: textper line, the timestamp being the offset from the call start.HH:MM:SS.mmm --> ...+<v Speaker>text) so the transcript loads as a caption track / imports elsewhere. The stored segment timestamps are only second-resolution (several can share a second), so cue times are forced monotonic - each cue starts atmax(real offset, prev end)and ends at the next cue's start (last gets a short tail) - guaranteeing valid, ordered, non-zero-length cues. Cue text is HTML-escaped.Search
The archive (
/transcripts) gains a?q=filter: list only transcripts containing a segment matching the query (case-insensitive, LIKE-escapedEXISTS), still access-gated per row. A search box on the archive page echoes the query; a distinct empty state shows when a query matches nothing (vs the "no transcripts yet" state).Testing
tests/transcripts.rs-> 10 green:export_txt_and_vtt(content + WEBVTT/-->shape + non-member 403) andarchive_search_filters_by_content(matching lists, non-matching excludes).just check,just test,just test-saasall exit 0;just build-cssregenerated.Out of scope (optional follow-up)
The LLM summary / action-items piece. It needs an operator-configured LLM endpoint - a new
[operator-action]config + mockable client mirroring the Phase 3 STT pattern, plus storage + UI - so it's a separate PR rather than crammed in here. Happy to do it next if wanted.No operator action, no migration.
The concrete, dependency-free core of LC-393 Phase 4 (browse landed in LC-394). Export: GET /transcripts/{id}/export?format=txt|vtt downloads a saved transcript, gated with the same access check as the transcript page, served as an attachment. txt is a header + "HH:MM:SS Speaker: text" per line (offset from the call start); vtt is standard WebVTT cues (<v Speaker>text) with forced-monotonic timings (the stored timestamps are second-resolution, so cue start = max(real offset, prev end) and end = next start, guaranteeing valid ordered cues). Download links on the transcript page. Search: the archive (/transcripts) gains a ?q= filter listing only transcripts containing a matching segment (case-insensitive, LIKE-escaped EXISTS), still access-gated per row. A search box on the archive page; a distinct empty state when a query matches nothing. db::transcripts::list_recent now takes an optional query; new routes::transcripts::export with txt/vtt builders; IndexQuery/ExportQuery; en/es strings; export + search tests. The optional LLM summary / action-items piece (needs an operator-configured endpoint, mirroring the Phase 3 STT pattern) is intentionally left as a separate follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>