diff --git a/internal/web/uploads.go b/internal/web/uploads.go index 1f5e90c..6f57637 100644 --- a/internal/web/uploads.go +++ b/internal/web/uploads.go @@ -3,7 +3,9 @@ package web import ( "context" "errors" + "fmt" "io" + "math" "net/http" "path/filepath" "strings" @@ -271,3 +273,26 @@ func (a *App) uploadChatView(u domain.UploadedTranscript, model string, history Subject: "recording", } } + +// maxSpotsShown is how many contested spots the page lists. The stored list holds up to +// 200 and the counts are exact; a page of two hundred "check this" items is not a page +// anyone reads, so the rest are counted, not listed. +const maxSpotsShown = 50 + +// minuteMark renders where in the recording a spot is, as the nearest minute. +func minuteMark(seconds float64) string { + return fmt.Sprintf("~%d min", int(math.Round(seconds/60))) +} + +// shownSpots is the part of the contested list the page renders. +func shownSpots(r *domain.Reconciliation) []domain.ContestedSpot { + if len(r.Contested) > maxSpotsShown { + return r.Contested[:maxSpotsShown] + } + return r.Contested +} + +// moreSpots is how many spots to check are not listed, from the exact counts. +func moreSpots(r *domain.Reconciliation) int { + return max(0, r.Changed+r.Unsure-len(shownSpots(r))) +} diff --git a/internal/web/uploads.templ b/internal/web/uploads.templ index f4bf11b..81ec0f7 100644 --- a/internal/web/uploads.templ +++ b/internal/web/uploads.templ @@ -2,6 +2,7 @@ package web import ( "fmt" + "strconv" "time" "git.d-ma.be/mathias/tapir/internal/domain" @@ -65,6 +66,7 @@ templ uploadStatus(u domain.UploadedTranscript, chatEnabled bool) { Transcribed { uploads.FormatDuration(time.Duration(u.AudioSeconds * float64(time.Second))) } of audio by { u.Endpoint }.
+ @reconciliationNote(u.Reconciliation) if chatEnabled { } @@ -123,3 +125,48 @@ templ videoNotice(hadVideo bool) { } } + +// reconciliationNote says what was done when two engines transcribed the audio +// (ADR-034), and never claims a second opinion that was not there: nil means a single +// engine and renders nothing. Spots the model changed or was unsure about are listed +// with a minute mark and both readings so the user can check them against the audio. +templ reconciliationNote(r *domain.Reconciliation) { + if r != nil { + switch r.Status { + case domain.ReconcileDone: ++ Reconciled from two transcripts ({ r.Base } and { r.Alt }): + { strconv.Itoa(r.Disputes) } differences, + { strconv.Itoa(r.AutoResolved) } settled automatically, + { strconv.Itoa(r.Adjudicated) } decided by the model ({ r.Reconciler }), + { strconv.Itoa(r.Unsure) } uncertain. +
+ if len(r.Contested) > 0 { +and { strconv.Itoa(moreSpots(r)) } more not listed
+ } +Only one engine produced this transcript ({ r.Reason }), so there was no second opinion to reconcile it with.
+ case domain.ReconcileSkipped: +Two transcripts were made but could not be reconciled ({ r.Reason }); the text shown is { r.Base }'s, unreconciled.
+ } + } +} diff --git a/internal/web/uploads_templ.go b/internal/web/uploads_templ.go index 396b7f2..801ec56 100644 --- a/internal/web/uploads_templ.go +++ b/internal/web/uploads_templ.go @@ -5,11 +5,14 @@ package web //lint:file-ignore SA4006 This context is only used if a nested component is present. -import "github.com/a-h/templ" -import templruntime "github.com/a-h/templ/runtime" +import ( + "github.com/a-h/templ" + templruntime "github.com/a-h/templ/runtime" +) import ( "fmt" + "strconv" "time" "git.d-ma.be/mathias/tapir/internal/domain" @@ -57,7 +60,7 @@ func UploadsPage(v uploadsView) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d MB", v.Limits.MaxBytes>>20)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 17, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 18, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -70,7 +73,7 @@ func UploadsPage(v uploadsView) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(uploads.FormatDuration(v.Limits.MaxDuration)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 18, Col: 50} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 19, Col: 50} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -88,7 +91,7 @@ func UploadsPage(v uploadsView) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(v.Error) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 22, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 23, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -116,7 +119,7 @@ func UploadsPage(v uploadsView) templ.Component { var templ_7745c5c3_Var6 templ.SafeURL templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/uploads/" + u.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 35, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 36, Col: 51} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -129,7 +132,7 @@ func UploadsPage(v uploadsView) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(u.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 35, Col: 63} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 36, Col: 63} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -142,7 +145,7 @@ func UploadsPage(v uploadsView) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(string(u.Status)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 36, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 37, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -213,7 +216,7 @@ func UploadPage(u domain.UploadedTranscript, chatEnabled bool) templ.Component { var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(u.Title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 50, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 51, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -281,7 +284,7 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool) templ.Component var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(uploads.FormatDuration(time.Duration(u.AudioSeconds * float64(time.Second)))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 65, Col: 95} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 66, Col: 95} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -294,7 +297,7 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool) templ.Component var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(u.Endpoint) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 66, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 67, Col: 48} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -304,6 +307,10 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool) templ.Component if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + templ_7745c5c3_Err = reconciliationNote(u.Reconciliation).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } if chatEnabled { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "