diff --git a/internal/web/uploads.templ b/internal/web/uploads.templ index 81ec0f7..fdca943 100644 --- a/internal/web/uploads.templ +++ b/internal/web/uploads.templ @@ -45,11 +45,11 @@ templ UploadsPage(v uploadsView) { } // UploadPage is one upload's page: its title and the polling status region. -templ UploadPage(u domain.UploadedTranscript, chatEnabled bool) { +templ UploadPage(u domain.UploadedTranscript, chatEnabled bool, now time.Time) { @Layout("Tapir — " + u.Title) {

{ u.Title }

- @uploadStatus(u, chatEnabled) + @uploadStatus(u, chatEnabled, now)

All recordings

} @@ -57,7 +57,7 @@ templ UploadPage(u domain.UploadedTranscript, chatEnabled bool) { // uploadStatus is the region HTMX polls. Unfinished states carry hx-trigger so the // page re-asks; done and failed render without it, so nothing polls forever. -templ uploadStatus(u domain.UploadedTranscript, chatEnabled bool) { +templ uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time) { switch u.Status { case domain.UploadDone:
@@ -67,6 +67,12 @@ templ uploadStatus(u domain.UploadedTranscript, chatEnabled bool) { of audio by { u.Endpoint }.

@reconciliationNote(u.Reconciliation) + if len(u.Progress) > 0 { +
+ What was done + @progressSteps(u.Progress, now, false) +
+ } if chatEnabled {

Ask about this recording

} @@ -79,6 +85,12 @@ templ uploadStatus(u domain.UploadedTranscript, chatEnabled bool) { default:
@videoNotice(u.HadVideo) if u.Status == domain.UploadTranscribing { -

Transcribing… a long recording takes a few minutes.

+ {{ health, silent := uploadHealth(u, now) }} + if len(u.Progress) > 0 { + @progressSteps(u.Progress, now, health != healthLost) + @healthNote(health, silent) + } else { +

Transcribing… a long recording takes a few minutes.

+ } } else {

Queued — waiting for its turn to be transcribed.

} @@ -170,3 +188,54 @@ templ reconciliationNote(r *domain.Reconciliation) { } } } + +// progressSteps is the job's phases as a log that fills in as it runs: finished ones +// collapse to a line with how long they took, the running one has a spinner and its +// elapsed time, and what has not started is dimmed. live is false when the spinner would +// be a lie (the service is lost, or the job is over), so the running phase shows a +// warning mark instead. Only a counted phase gets a bar (ADR-035). +templ progressSteps(steps []domain.ProgressStep, now time.Time, live bool) { +
    + for _, s := range steps { +
  1. + if s.State == domain.StepRunning && live { + + } else if s.State == domain.StepRunning { + + } else { + + } + { s.Label } + if t := stepTime(s, now); t != "" { + { t } + } + if s.State == domain.StepRunning && s.Total > 0 { + + { strconv.Itoa(s.Done) } of { strconv.Itoa(s.Total) } + } +
  2. + } +
+} + +// healthNote says so when the job is not simply running: the service stopped reporting, +// or a counted phase has gone quiet. Running says nothing: the spinner does. +templ healthNote(h jobHealth, silent time.Duration) { + switch h { + case healthLost: + + case healthQuiet: +

+ Nothing new for { minutesAgo(silent) }: the model may be slow. The service is still running, + and the job gives up on its own if it takes too long. +

+ } +} diff --git a/internal/web/uploads_templ.go b/internal/web/uploads_templ.go index 801ec56..c49ef2a 100644 --- a/internal/web/uploads_templ.go +++ b/internal/web/uploads_templ.go @@ -176,7 +176,7 @@ func UploadsPage(v uploadsView) templ.Component { } // UploadPage is one upload's page: its title and the polling status region. -func UploadPage(u domain.UploadedTranscript, chatEnabled bool) templ.Component { +func UploadPage(u domain.UploadedTranscript, chatEnabled bool, now time.Time) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -226,7 +226,7 @@ func UploadPage(u domain.UploadedTranscript, chatEnabled bool) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = uploadStatus(u, chatEnabled).Render(ctx, templ_7745c5c3_Buffer) + templ_7745c5c3_Err = uploadStatus(u, chatEnabled, now).Render(ctx, templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -246,7 +246,7 @@ func UploadPage(u domain.UploadedTranscript, chatEnabled bool) templ.Component { // uploadStatus is the region HTMX polls. Unfinished states carry hx-trigger so the // page re-asks; done and failed render without it, so nothing polls forever. -func uploadStatus(u domain.UploadedTranscript, chatEnabled bool) templ.Component { +func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -311,44 +311,58 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool) templ.Component if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + if len(u.Progress) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "
What was done") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = progressSteps(u.Progress, now, false).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } if chatEnabled { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

Ask about this recording

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">Ask about this recording

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
Transcript
")
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
Transcript
")
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
 			var templ_7745c5c3_Var16 string
 			templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(u.Content)
 			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 75, Col: 40}
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 81, Col: 40}
 			}
 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } case domain.UploadFailed: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -356,51 +370,69 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool) templ.Component if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(uploadFailureMessage(u.ErrorKind)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 81, Col: 42} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 87, Col: 42} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(u.Progress) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "
Where it stopped") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = progressSteps(u.Progress, now, false).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } default: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" hx-swap=\"outerHTML\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -409,17 +441,33 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool) templ.Component return templ_7745c5c3_Err } if u.Status == domain.UploadTranscribing { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "

Transcribing… a long recording takes a few minutes.

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err + health, silent := uploadHealth(u, now) + if len(u.Progress) > 0 { + templ_7745c5c3_Err = progressSteps(u.Progress, now, health != healthLost).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = healthNote(health, silent).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "

Transcribing… a long recording takes a few minutes.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "

Queued — waiting for its turn to be transcribed.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

Queued — waiting for its turn to be transcribed.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -463,20 +511,20 @@ func UploadChatPage(u domain.UploadedTranscript, v chatView) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var22 string templ_7745c5c3_Var22, 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: 106, Col: 16} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 124, Col: 16} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "

Ask about this recording

Answers come only from this recording's transcript, which is private to you.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

Ask about this recording

Answers come only from this recording's transcript, which is private to you.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -484,20 +532,20 @@ func UploadChatPage(u domain.UploadedTranscript, v chatView) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "

Back to the transcript

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "\">Back to the transcript

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -537,7 +585,7 @@ func videoNotice(hadVideo bool) templ.Component { } ctx = templ.ClearChildren(ctx) if hadVideo { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

This file contained a video track. Only its audio was used; the video was discarded and nothing from it was kept.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "

This file contained a video track. Only its audio was used; the video was discarded and nothing from it was kept.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -574,262 +622,262 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component { if r != nil { switch r.Status { case domain.ReconcileDone: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "

Reconciled from two transcripts (") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "

Reconciled from two transcripts (") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(r.Base) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 139, Col: 47} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 157, Col: 47} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " and ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " and ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var27 string templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(r.Alt) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 139, Col: 61} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 157, Col: 61} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "): ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "): ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var28 string templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(r.Disputes)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 140, Col: 32} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 158, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, " differences, ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, " differences, ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var29 string templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(r.AutoResolved)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 141, Col: 36} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 159, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, " settled automatically, ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, " settled automatically, ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var30 string templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(r.Adjudicated)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 142, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 160, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, " decided by the model (") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " decided by the model (") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var31 string templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(r.Reconciler) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 142, Col: 74} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 160, Col: 74} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "), ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "), ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var32 string templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(r.Unsure)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 143, Col: 30} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 161, Col: 30} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " uncertain.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, " uncertain.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(r.Contested) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
Spots to check against the audio
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
    Spots to check against the audio
      ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, c := range shownSpots(r) { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "
    • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
    • ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var33 string templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(minuteMark(c.Seconds)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 151, Col: 33} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 169, Col: 33} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, ": ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, ": ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var34 string templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(c.Base) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 151, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 169, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " / ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, " / ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var35 string templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(c.Alt) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 151, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 169, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, " — ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, " — ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if c.Unsure { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "uncertain kept ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "uncertain kept ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var36 string templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(c.Used) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 153, Col: 70} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 171, Col: 70} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "changed used ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "changed used ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var37 string templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(c.Used) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 155, Col: 58} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 173, Col: 58} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
    • ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "
    ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if moreSpots(r) > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "

and ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "

and ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var38 string templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(moreSpots(r))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 161, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 179, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, " more not listed

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, " more not listed

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } case domain.ReconcileSingle: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "

Only one engine produced this transcript (") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "

Only one engine produced this transcript (") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var39 string templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(r.Reason) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 167, Col: 88} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 185, Col: 88} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "), so there was no second opinion to reconcile it with.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "), so there was no second opinion to reconcile it with.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } case domain.ReconcileSkipped: - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "

Two transcripts were made but could not be reconciled (") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "

Two transcripts were made but could not be reconciled (") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var40 string templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(r.Reason) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 169, Col: 101} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 187, Col: 101} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "); the text shown is ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "); the text shown is ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var41 string templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(r.Base) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 169, Col: 132} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 187, Col: 132} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "'s, unreconciled.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "'s, unreconciled.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -839,4 +887,268 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component { }) } +// progressSteps is the job's phases as a log that fills in as it runs: finished ones +// collapse to a line with how long they took, the running one has a spinner and its +// elapsed time, and what has not started is dimmed. live is false when the spinner would +// be a lie (the service is lost, or the job is over), so the running phase shows a +// warning mark instead. Only a counted phase gets a bar (ADR-035). +func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var42 := templ.GetChildren(ctx) + if templ_7745c5c3_Var42 == nil { + templ_7745c5c3_Var42 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, s := range steps { + templ_7745c5c3_Var43 := []any{"step step-" + string(s.State)} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var43...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "
  1. ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if s.State == domain.StepRunning && live { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else if s.State == domain.StepRunning { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "! ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var45 string + templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(stepMark(s)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 211, Col: 61} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var46 string + templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(s.Label) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 213, Col: 38} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if t := stepTime(s, now); t != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var47 string + templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(t) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 215, Col: 32} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if s.State == domain.StepRunning && s.Total > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var50 string + templ_7745c5c3_Var50, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(s.Done)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 219, Col: 52} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var50)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, " of ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var51 string + templ_7745c5c3_Var51, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(s.Total)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 219, Col: 81} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var51)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "
  2. ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +// healthNote says so when the job is not simply running: the service stopped reporting, +// or a counted phase has gone quiet. Running says nothing: the spinner does. +func healthNote(h jobHealth, silent time.Duration) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var52 := templ.GetChildren(ctx) + if templ_7745c5c3_Var52 == nil { + templ_7745c5c3_Var52 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + switch h { + case healthLost: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "

The service stopped reporting ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var53 string + templ_7745c5c3_Var53, templ_7745c5c3_Err = templ.JoinStringErrs(minutesAgo(silent)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 232, Col: 54} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var53)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, " ago. It may have restarted or crashed. This page keeps checking; if nothing changes, upload the recording again.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case healthQuiet: + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "

Nothing new for ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var54 string + templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(minutesAgo(silent)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/uploads.templ`, Line: 237, Col: 40} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, ": the model may be slow. The service is still running, and the job gives up on its own if it takes too long.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + var _ = templruntime.GeneratedTemplate