feat(web): pipeline stats bar, summarized-first sort, Try now button for rate-limited videos
Three UX improvements for the pending-transcript state:
1. Summarized videos sort to top (ORDER BY (s.id IS NOT NULL) DESC, seen_at DESC)
so completed summaries are always immediately visible without filtering.
ListVideos default limit raised from 50 to 500 to show the full backlog.
2. Pipeline stats bar above the video list: '2 summarized · 256 fetching soon · 12
no captions' — computed from the unfiltered row set, hidden when everything is
summarized.
3. 'Try now' button on rate-limited cards replaces the passive 'Retrying later'
chip. POST /v/{id}/retry-now clears rate_limited_at then calls ProcessVideo
through the shared globalFetchGate — same rate limiting as the scheduler, safe
under concurrent use.
This commit is contained in:
@@ -104,16 +104,36 @@ templ flashBanner(code string) {
|
||||
// #summary-list region; a non-HTMX request renders the whole page. flash carries
|
||||
// a one-shot notification (e.g. "connected", "registered") surfaced on arrival
|
||||
// after a POST→redirect.
|
||||
templ ListPage(rows []store.SummaryRow, f Filter, flash string, hasConnected bool) {
|
||||
templ ListPage(rows []store.SummaryRow, f Filter, stats PipelineStats, flash string, hasConnected bool) {
|
||||
@Layout("Tapir — Summaries") {
|
||||
@flashBanner(flash)
|
||||
@filterForm(f)
|
||||
if stats.RateLimited > 0 || stats.Pending > 0 || stats.NoText > 0 {
|
||||
@pipelineBar(stats)
|
||||
}
|
||||
<div id="summary-list">
|
||||
@summaryList(rows, hasConnected)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ pipelineBar(s PipelineStats) {
|
||||
<div class="pipeline-bar">
|
||||
if s.Summarized > 0 {
|
||||
<span>{ fmt.Sprintf("%d summarized", s.Summarized) }</span>
|
||||
}
|
||||
if s.RateLimited > 0 {
|
||||
<span>{ fmt.Sprintf("%d fetching soon", s.RateLimited) }</span>
|
||||
}
|
||||
if s.Pending > 0 {
|
||||
<span>{ fmt.Sprintf("%d pending", s.Pending) }</span>
|
||||
}
|
||||
if s.NoText > 0 {
|
||||
<span class="muted">{ fmt.Sprintf("%d no captions", s.NoText) }</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
templ filterForm(f Filter) {
|
||||
<form
|
||||
class="filters"
|
||||
@@ -194,7 +214,16 @@ templ VideoCard(r store.SummaryRow) {
|
||||
<span class="card-state">{ strings.Join(r.Actions, ", ") }</span>
|
||||
}
|
||||
} else if r.TranscriptStatus == "rate_limited" {
|
||||
<span class="chip chip-retry" title="Caption fetch was rate-limited; tapir will retry automatically.">⏳ Retrying later</span>
|
||||
<form
|
||||
method="post"
|
||||
action={ retryNowURL(r.VideoID) }
|
||||
hx-post={ string(retryNowURL(r.VideoID)) }
|
||||
hx-target={ "#video-" + r.VideoID }
|
||||
hx-swap="outerHTML"
|
||||
class="retry-form"
|
||||
>
|
||||
<button type="submit" class="btn-retry" title="Fetch transcript now through the shared rate gate">Try now</button>
|
||||
</form>
|
||||
} else if r.SummarizeRequested {
|
||||
<span class="chip">Queued</span>
|
||||
<span class="card-state muted">waiting for the next run</span>
|
||||
|
||||
Reference in New Issue
Block a user