package web import ( "fmt" "strconv" "time" "git.d-ma.be/mathias/tapir/internal/domain" "git.d-ma.be/mathias/tapir/internal/uploads" ) // UploadsPage is the upload form plus the caller's own uploads (ADR-033). templ UploadsPage(v uploadsView) { @Layout("Tapir — Upload a recording") {

Upload a recording

Audio only. Up to { fmt.Sprintf("%d MB", v.Limits.MaxBytes>>20) } and { uploads.FormatDuration(v.Limits.MaxDuration) }. The transcript is private to you; the audio is deleted once it has been transcribed.

if v.Error != "" { }

if len(v.Rows) > 0 {

Your recordings

}
} } // UploadPage is one upload's page: its title and the polling status region. templ UploadPage(u domain.UploadedTranscript, chatEnabled bool) { @Layout("Tapir — " + u.Title) {

{ u.Title }

@uploadStatus(u, chatEnabled)

All recordings

} } // 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) { switch u.Status { case domain.UploadDone:
@videoNotice(u.HadVideo)

Transcribed { uploads.FormatDuration(time.Duration(u.AudioSeconds * float64(time.Second))) } of audio by { u.Endpoint }.

@reconciliationNote(u.Reconciliation) if chatEnabled {

Ask about this recording

}
Transcript
{ u.Content }
case domain.UploadFailed: default:
@videoNotice(u.HadVideo) if u.Status == domain.UploadTranscribing {

Transcribing… a long recording takes a few minutes.

} else {

Queued — waiting for its turn to be transcribed.

}
} } // UploadChatPage is the no-JS full page for chatting with an uploaded transcript: // the same chat panel as a video's (ADR-027), scoped to this recording. templ UploadChatPage(u domain.UploadedTranscript, v chatView) { @Layout("Tapir — Ask about " + u.Title) {

{ u.Title }

Ask about this recording

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

@chatPanel(v)

Back to the transcript

} } // videoNotice tells the user a video track was present and what became of it. A // Teams meeting with the camera off is still a video file; it is accepted, only the // audio is transcribed, and the picture is thrown away — so say so, in every state, // instead of leaving the user to wonder (ADR-033). templ videoNotice(hadVideo bool) { if hadVideo {

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

} } // 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 {
Spots to check against the audio if moreSpots(r) > 0 {

and { strconv.Itoa(moreSpots(r)) } more not listed

}
}
case domain.ReconcileSingle:

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.

} } }