Compare commits
278
Commits
+1
-7
@@ -1751,13 +1751,7 @@ wording while only `content` is merged.
|
|||||||
never a stuck row or a server error.
|
never a stuck row or a server error.
|
||||||
- **The configured upload chat model is callable.** It is added to the shared chat service's model
|
- **The configured upload chat model is callable.** It is added to the shared chat service's model
|
||||||
list when uploads are on and it is local, because the service re-maps a model it does not offer
|
list when uploads are on and it is local, because the service re-maps a model it does not offer
|
||||||
(a valid `iguana/gemma4-31b` was being silently swapped for `koala/phi4-mini`). The video chat's own switcher
|
(a valid `iguana/gemma4-31b` was being silently swapped for `koala/phi4-mini`).
|
||||||
lists that local model too while uploads are on.
|
|
||||||
- **A client that hangs up mid-upload is not an error**: the handler logs INFO and writes nothing
|
|
||||||
(rendering the error page ran a database read on a cancelled context and logged a false ERROR). The
|
|
||||||
400 "cut off" answer is keyed on a marker for errors from *reading the client's body*
|
|
||||||
(`uploads.ErrClientRead`), not on `io.ErrUnexpectedEOF`, which is also what a database drop looks like.
|
|
||||||
- **A note is not hidden**: a finished upload's record opens by default when any step carries a note.
|
|
||||||
- **Bad input is a 4xx**: a non-UUID id is a 404, a malformed or cut-off multipart body is a 400
|
- **Bad input is a 4xx**: a non-UUID id is a 404, a malformed or cut-off multipart body is a 400
|
||||||
whose text is not logged, and a title with control bytes, bidirectional controls or invalid UTF-8 is
|
whose text is not logged, and a title with control bytes, bidirectional controls or invalid UTF-8 is
|
||||||
cleaned (emoji joiners, Swedish letters and variation selectors are kept), not a 500.
|
cleaned (emoji joiners, Swedish letters and variation selectors are kept), not a 500.
|
||||||
|
|||||||
@@ -17,20 +17,13 @@ import (
|
|||||||
// HTTPStatuser is implemented by errors that are a non-2xx answer from a service.
|
// HTTPStatuser is implemented by errors that are a non-2xx answer from a service.
|
||||||
type HTTPStatuser interface{ HTTPStatus() int }
|
type HTTPStatuser interface{ HTTPStatus() int }
|
||||||
|
|
||||||
// SQLStater is implemented by database errors that carry a SQLSTATE (pgx's PgError does).
|
// Of is the kind of err: "timeout", "canceled", "http_<code>", "network", or "error".
|
||||||
// The five-character code is a fixed vocabulary; the error's message is not: Postgres quotes
|
|
||||||
// the offending value, which for a transcript row is transcript text.
|
|
||||||
type SQLStater interface{ SQLState() string }
|
|
||||||
|
|
||||||
// Of is the kind of err: "timeout", "canceled", "http_<code>", "sql_<state>", "network",
|
|
||||||
// or "error".
|
|
||||||
// It is empty for nil.
|
// It is empty for nil.
|
||||||
func Of(err error) string {
|
func Of(err error) string {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
var hs HTTPStatuser
|
var hs HTTPStatuser
|
||||||
var ss SQLStater
|
|
||||||
var ne net.Error
|
var ne net.Error
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, context.DeadlineExceeded):
|
case errors.Is(err, context.DeadlineExceeded):
|
||||||
@@ -39,8 +32,6 @@ func Of(err error) string {
|
|||||||
return "canceled"
|
return "canceled"
|
||||||
case errors.As(err, &hs):
|
case errors.As(err, &hs):
|
||||||
return fmt.Sprintf("http_%d", hs.HTTPStatus())
|
return fmt.Sprintf("http_%d", hs.HTTPStatus())
|
||||||
case errors.As(err, &ss) && ss.SQLState() != "":
|
|
||||||
return "sql_" + ss.SQLState()
|
|
||||||
case errors.As(err, &ne):
|
case errors.As(err, &ne):
|
||||||
if ne.Timeout() {
|
if ne.Timeout() {
|
||||||
return "timeout"
|
return "timeout"
|
||||||
|
|||||||
@@ -54,24 +54,6 @@ func TestOf_ReducesAnErrorToAKindAndNeverItsText(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type sqlErr struct{}
|
|
||||||
|
|
||||||
func (sqlErr) Error() string {
|
|
||||||
return `ERROR: invalid input syntax for type json: "Anders ansvarar för virket"`
|
|
||||||
}
|
|
||||||
func (sqlErr) SQLState() string { return "22P02" }
|
|
||||||
|
|
||||||
func TestOf_ADatabaseErrorIsReducedToItsSQLStateNeverItsMessage(t *testing.T) {
|
|
||||||
// A Postgres error can quote the offending value, which for a transcript row is
|
|
||||||
// transcript text. The five-character SQLSTATE is a fixed vocabulary (fourth review).
|
|
||||||
err := fmt.Errorf("store: complete upload: %w", sqlErr{})
|
|
||||||
|
|
||||||
got := errkind.Of(err)
|
|
||||||
|
|
||||||
assert.Equal(t, "sql_22P02", got)
|
|
||||||
assert.NotContains(t, got, "Anders")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOf_AKindCarriesNoWordFromTheError(t *testing.T) {
|
func TestOf_AKindCarriesNoWordFromTheError(t *testing.T) {
|
||||||
err := fmt.Errorf("convert /spool/upload-555: %w", errors.New("Anders ansvarar för virket"))
|
err := fmt.Errorf("convert /spool/upload-555: %w", errors.New("Anders ansvarar för virket"))
|
||||||
|
|
||||||
|
|||||||
@@ -58,27 +58,6 @@ func NewIntake(s Store, p Prober, sub Submitter, l Limits, tmpDir string) *Intak
|
|||||||
return &Intake{store: s, prober: p, sub: sub, limits: l, tmpDir: tmpDir}
|
return &Intake{store: s, prober: p, sub: sub, limits: l, tmpDir: tmpDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrClientRead marks an error that came from READING the client's body: a connection that
|
|
||||||
// dropped, a body cut short. It is the sender's to retry, unlike a spool, probe or database
|
|
||||||
// fault, which is ours. It is a marker on the error, not a sentinel that others may wrap:
|
|
||||||
// io.ErrUnexpectedEOF alone is also what a database drop looks like.
|
|
||||||
var ErrClientRead = errors.New("uploads: client body read failed")
|
|
||||||
|
|
||||||
// clientReader remembers the last non-EOF error its source returned, so Accept can tell
|
|
||||||
// a failure reading the client from a failure writing the spool file.
|
|
||||||
type clientReader struct {
|
|
||||||
r io.Reader
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *clientReader) Read(p []byte) (int, error) {
|
|
||||||
n, err := c.r.Read(p)
|
|
||||||
if err != nil && !errors.Is(err, io.EOF) {
|
|
||||||
c.err = err
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// busyMessage is what a user is told when the queue is full.
|
// busyMessage is what a user is told when the queue is full.
|
||||||
const busyMessage = "Too many recordings are waiting to be transcribed. Please try again in a few minutes."
|
const busyMessage = "Too many recordings are waiting to be transcribed. Please try again in a few minutes."
|
||||||
|
|
||||||
@@ -107,15 +86,11 @@ func (in *Intake) Accept(ctx context.Context, userID, title string, src io.Reade
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Read one byte past the cap so "exactly at the limit" and "over" are told apart.
|
// Read one byte past the cap so "exactly at the limit" and "over" are told apart.
|
||||||
cr := &clientReader{r: io.LimitReader(src, in.limits.MaxBytes+1)}
|
n, err := io.Copy(f, io.LimitReader(src, in.limits.MaxBytes+1))
|
||||||
n, err := io.Copy(f, cr)
|
|
||||||
if cerr := f.Close(); err == nil {
|
if cerr := f.Close(); err == nil {
|
||||||
err = cerr
|
err = cerr
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if cr.err != nil && errors.Is(err, cr.err) {
|
|
||||||
return "", fmt.Errorf("%w: %w", ErrClientRead, err)
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("uploads: receive: %w", err)
|
return "", fmt.Errorf("uploads: receive: %w", err)
|
||||||
}
|
}
|
||||||
if n > in.limits.MaxBytes {
|
if n > in.limits.MaxBytes {
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package uploads_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -66,31 +64,3 @@ func TestAccept_ASubmitterThatCannotSayStillWorksAsBefore(t *testing.T) {
|
|||||||
|
|
||||||
require.NoError(t, err, "a Submitter without HasRoom is accepted: the check at Submit remains")
|
require.NoError(t, err, "a Submitter without HasRoom is accepted: the check at Submit remains")
|
||||||
}
|
}
|
||||||
|
|
||||||
// brokenBody is a client body that fails mid-read, like a connection that dropped.
|
|
||||||
type brokenBody struct{ n int }
|
|
||||||
|
|
||||||
func (b *brokenBody) Read(p []byte) (int, error) {
|
|
||||||
if b.n > 0 {
|
|
||||||
b.n = 0
|
|
||||||
return copy(p, "partial audio"), nil
|
|
||||||
}
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccept_AnErrorReadingTheClientsBodyIsMarkedAsTheClientsAndAWrappedSubmitErrorIsNot(t *testing.T) {
|
|
||||||
r := newRig(t)
|
|
||||||
|
|
||||||
_, err := r.in.Accept(context.Background(), "user-1", "t", &brokenBody{n: 1})
|
|
||||||
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.ErrorIs(t, err, uploads.ErrClientRead, "a dropped client body is the sender's to retry")
|
|
||||||
assert.Empty(t, r.tmpFiles(t))
|
|
||||||
|
|
||||||
// A fault that is ours must NOT carry the marker, even if it wraps the same sentinel.
|
|
||||||
r2 := newRig(t)
|
|
||||||
r2.sub.err = fmt.Errorf("db: %w", io.ErrUnexpectedEOF)
|
|
||||||
_, err = r2.in.Accept(context.Background(), "user-1", "t", strings.NewReader("data"))
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.NotErrorIs(t, err, uploads.ErrClientRead)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ func (w *Worker) Process(ctx context.Context, job Job) {
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
if err := w.store.StartTranscribing(ctx, job.UserID, job.UploadID); err != nil {
|
if err := w.store.StartTranscribing(ctx, job.UserID, job.UploadID); err != nil {
|
||||||
log.Error("upload: start transcribing", "kind", errkind.Of(err))
|
log.Error("upload: start transcribing", "err", err)
|
||||||
w.fail(ctx, job, log, domain.UploadErrTranscriptionFailed)
|
w.fail(ctx, job, log, domain.UploadErrTranscriptionFailed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -279,7 +279,7 @@ func (w *Worker) Process(ctx context.Context, job Job) {
|
|||||||
|
|
||||||
wav, err := w.tempWAV()
|
wav, err := w.tempWAV()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("upload: temp wav", "kind", errkind.Of(err))
|
log.Error("upload: temp wav", "err", err)
|
||||||
abort(domain.UploadErrTranscriptionFailed)
|
abort(domain.UploadErrTranscriptionFailed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -322,7 +322,7 @@ func (w *Worker) Process(ctx context.Context, job Job) {
|
|||||||
|
|
||||||
tr.Begin("save", 0)
|
tr.Begin("save", 0)
|
||||||
if err := w.store.CompleteUpload(ctx, job.UserID, job.UploadID, t); err != nil {
|
if err := w.store.CompleteUpload(ctx, job.UserID, job.UploadID, t); err != nil {
|
||||||
log.Error("upload: store transcript", "kind", errkind.Of(err))
|
log.Error("upload: store transcript", "err", err)
|
||||||
abort(domain.UploadErrTranscriptionFailed)
|
abort(domain.UploadErrTranscriptionFailed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -339,7 +339,7 @@ func (w *Worker) fail(ctx context.Context, job Job, log *slog.Logger, kind domai
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := w.store.FailUpload(ctx, job.UserID, job.UploadID, kind); err != nil {
|
if err := w.store.FailUpload(ctx, job.UserID, job.UploadID, kind); err != nil {
|
||||||
log.Error("upload: record failure", "kind", errkind.Of(err))
|
log.Error("upload: record failure", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,8 +104,6 @@ func TestWorkerDuration_TheShortfallNoteIsPinnedAtEightyPercentAndTwoMinutes(t *
|
|||||||
noted bool
|
noted bool
|
||||||
}{
|
}{
|
||||||
{"79 percent of an hour", 60 * time.Minute, 0.79 * 3600, true},
|
{"79 percent of an hour", 60 * time.Minute, 0.79 * 3600, true},
|
||||||
{"exactly 80 percent of an hour", 60 * time.Minute, 2880, false},
|
|
||||||
{"one second under 80 percent of an hour", 60 * time.Minute, 2879, true},
|
|
||||||
{"81 percent of an hour", 60 * time.Minute, 0.81 * 3600, false},
|
{"81 percent of an hour", 60 * time.Minute, 0.81 * 3600, false},
|
||||||
{"exactly two minutes short and under 80 percent", 6 * time.Minute, 4 * 60, false},
|
{"exactly two minutes short and under 80 percent", 6 * time.Minute, 4 * 60, false},
|
||||||
{"two minutes and a second short and under 80 percent", 6 * time.Minute, 4*60 - 1, true},
|
{"two minutes and a second short and under 80 percent", 6 * time.Minute, 4*60 - 1, true},
|
||||||
|
|||||||
@@ -197,19 +197,3 @@ func TestWorkerProgress_AFailureLogsAKindNeverTheErrorTextOrThePath(t *testing.T
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkerProgress_AStoreFailureLogsItsSQLStateNeverTheValueItQuotes(t *testing.T) {
|
|
||||||
// A Postgres error can quote the offending value; for a transcript row that is the
|
|
||||||
// transcript (fourth adversarial review). The log gets the SQLSTATE.
|
|
||||||
r := newWRig(t, time.Minute)
|
|
||||||
r.st.completeErr = dbErr{}
|
|
||||||
buf := &syncLog{}
|
|
||||||
w := newProgressWorker(t, r, r.tr, uploads.WithLogger(slog.New(slog.NewTextHandler(buf, nil))))
|
|
||||||
|
|
||||||
w.Process(context.Background(), r.spool(t, "a"))
|
|
||||||
|
|
||||||
out := buf.String()
|
|
||||||
assert.NotContains(t, out, "zebrakaka")
|
|
||||||
assert.Contains(t, out, "upload: store transcript")
|
|
||||||
assert.Contains(t, out, "kind=sql_22P02")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -90,19 +90,3 @@ func TestWorkerHasRoom_SaysWhetherASubmitWouldBeAccepted(t *testing.T) {
|
|||||||
require.NoError(t, submit(w, "bob", 1))
|
require.NoError(t, submit(w, "bob", 1))
|
||||||
assert.False(t, w.HasRoom("carol"), "the queue as a whole is at its cap")
|
assert.False(t, w.HasRoom("carol"), "the queue as a whole is at its cap")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkerHasRoom_AgreesWithSubmitAtEveryStepAndIsFalseOnceStopped(t *testing.T) {
|
|
||||||
w := queueWorker(t, 2, 1)
|
|
||||||
for i, user := range []string{"a", "a", "b", "c"} {
|
|
||||||
room := w.HasRoom(user)
|
|
||||||
err := submit(w, user, i)
|
|
||||||
assert.Equal(t, room, err == nil, "step %d (%s): HasRoom=%v but Submit returned %v", i, user, room, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := newWRig(t, time.Minute)
|
|
||||||
stopped := uploads.NewWorker(r.st, r.conv, r.tr, r.tmp, time.Minute)
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
cancel()
|
|
||||||
stopped.Run(ctx)
|
|
||||||
assert.False(t, stopped.HasRoom("a"), "a worker that has stopped has no room")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -324,11 +324,3 @@ func TestWorkerRun_StoppingDeletesQueuedAudioAndRefusesNewJobs(t *testing.T) {
|
|||||||
assert.Empty(t, r.leftovers(t), "audio of queued jobs is deleted on stop: the rows are swept to interrupted at next start")
|
assert.Empty(t, r.leftovers(t), "audio of queued jobs is deleted on stop: the rows are swept to interrupted at next start")
|
||||||
assert.ErrorIs(t, r.w.Submit(r.spool(t, "c")), uploads.ErrStopped)
|
assert.ErrorIs(t, r.w.Submit(r.spool(t, "c")), uploads.ErrStopped)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dbErr is an error shaped like pgx's: a message that quotes a value, and an SQLSTATE.
|
|
||||||
type dbErr struct{}
|
|
||||||
|
|
||||||
func (dbErr) Error() string {
|
|
||||||
return `ERROR: invalid input syntax for type json: "zebrakaka secret words"`
|
|
||||||
}
|
|
||||||
func (dbErr) SQLState() string { return "22P02" }
|
|
||||||
|
|||||||
+2
-37
@@ -95,9 +95,6 @@ func (a *App) handleUploadPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var tooBig *http.MaxBytesError
|
var tooBig *http.MaxBytesError
|
||||||
if a.abandoned(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !errors.As(err, &tooBig) {
|
if !errors.As(err, &tooBig) {
|
||||||
// A malformed multipart body (a header with a control byte, a truncated
|
// A malformed multipart body (a header with a control byte, a truncated
|
||||||
// part) is the sender's to fix: a 400, not a server error, and the error
|
// part) is the sender's to fix: a 400, not a server error, and the error
|
||||||
@@ -113,9 +110,6 @@ func (a *App) handleUploadPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
case "title":
|
case "title":
|
||||||
b, err := io.ReadAll(io.LimitReader(part, maxTitleRunes*utf8.UTFMax))
|
b, err := io.ReadAll(io.LimitReader(part, maxTitleRunes*utf8.UTFMax))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if a.abandoned(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "That upload could not be read. Please try again.")
|
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "That upload could not be read. Please try again.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -136,27 +130,10 @@ func (a *App) handleUploadPost(w http.ResponseWriter, r *http.Request) {
|
|||||||
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "Choose an audio file to upload.")
|
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "Choose an audio file to upload.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// abandoned reports, logs at INFO and answers 499, because the client has already gone: its request
|
|
||||||
// context is cancelled, so there is nobody to answer and rendering a page would run its
|
|
||||||
// database read on a dead context and log a false ERROR. A hang-up mid-upload is routine.
|
|
||||||
func (a *App) abandoned(w http.ResponseWriter, r *http.Request) bool {
|
|
||||||
if r.Context().Err() == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
a.logger().Info("upload abandoned", "reason", "client_gone")
|
|
||||||
// 499 (the "client closed request" convention): a client that half-closed after sending
|
|
||||||
// can still read, and an unwritten status would reach it as a false, empty 200.
|
|
||||||
w.WriteHeader(499)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// uploadFailure maps an Accept (or multipart) error to a response: a validation
|
// uploadFailure maps an Accept (or multipart) error to a response: a validation
|
||||||
// rejection is the user's to fix (422, or 413 for size) and carries its own
|
// rejection is the user's to fix (422, or 413 for size) and carries its own
|
||||||
// message; anything else is ours.
|
// message; anything else is ours.
|
||||||
func (a *App) uploadFailure(w http.ResponseWriter, r *http.Request, userID string, err error) {
|
func (a *App) uploadFailure(w http.ResponseWriter, r *http.Request, userID string, err error) {
|
||||||
if a.abandoned(w, r) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var rej *uploads.RejectError
|
var rej *uploads.RejectError
|
||||||
var tooBig *http.MaxBytesError
|
var tooBig *http.MaxBytesError
|
||||||
switch {
|
switch {
|
||||||
@@ -175,9 +152,8 @@ func (a *App) uploadFailure(w http.ResponseWriter, r *http.Request, userID strin
|
|||||||
a.logger().Info("upload rejected", "reason", string(uploads.ReasonTooLarge))
|
a.logger().Info("upload rejected", "reason", string(uploads.ReasonTooLarge))
|
||||||
a.renderUploadsPage(w, r, userID, http.StatusRequestEntityTooLarge,
|
a.renderUploadsPage(w, r, userID, http.StatusRequestEntityTooLarge,
|
||||||
"This file is larger than the upload limit.")
|
"This file is larger than the upload limit.")
|
||||||
case errors.Is(err, uploads.ErrClientRead):
|
case errors.Is(err, io.ErrUnexpectedEOF):
|
||||||
// An error reading the CLIENT's body: theirs to retry, not a server fault. Only that
|
// The sender hung up mid-file: theirs to retry, not a server fault.
|
||||||
// marker counts: io.ErrUnexpectedEOF alone is also what a database drop looks like.
|
|
||||||
a.logger().Info("upload rejected", "reason", "truncated")
|
a.logger().Info("upload rejected", "reason", "truncated")
|
||||||
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "The upload was cut off before it finished. Please try again.")
|
a.renderUploadsPage(w, r, userID, http.StatusBadRequest, "The upload was cut off before it finished. Please try again.")
|
||||||
default:
|
default:
|
||||||
@@ -243,17 +219,6 @@ func titleFromFilename(name string) string {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasNote reports whether any phase carries a note. A note is the only warning for a
|
|
||||||
// recording that is shorter than its file claimed, so a record that has one is shown open.
|
|
||||||
func hasNote(steps []domain.ProgressStep) bool {
|
|
||||||
for _, s := range steps {
|
|
||||||
if s.Note != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// uuidShape is the form of an upload id. Anything else cannot be one, so it is a 404.
|
// uuidShape is the form of an upload id. Anything else cannot be one, so it is a 404.
|
||||||
var uuidShape = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
|
var uuidShape = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`)
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ templ uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
</p>
|
</p>
|
||||||
@reconciliationNote(u.Reconciliation)
|
@reconciliationNote(u.Reconciliation)
|
||||||
if len(u.Progress) > 0 {
|
if len(u.Progress) > 0 {
|
||||||
<details class="what-was-done" if hasNote(u.Progress) { open }>
|
<details class="what-was-done">
|
||||||
<summary>What was done</summary>
|
<summary>What was done</summary>
|
||||||
@progressSteps(u.Progress, now, false)
|
@progressSteps(u.Progress, now, false)
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
package web_test
|
package web_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -62,22 +63,37 @@ func TestUploadPost_AMalformedMultipartHeaderIsA400AndItsTextIsNotLogged(t *test
|
|||||||
assert.NotContains(t, buf.String(), "zebrakaka", "a file name is private content")
|
assert.NotContains(t, buf.String(), "zebrakaka", "a file name is private content")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUploadPost_EveryBidirectionalControlCharacterIsStrippedFromATitle(t *testing.T) {
|
func TestUploadPost_ABodyCutOffInsideTheFilePartIsA400NotA500(t *testing.T) {
|
||||||
|
app, sub := newUploadApp(t, audioOK, 200<<20)
|
||||||
|
req := uploadReq(t, "t", "a.m4a", bytes.Repeat([]byte("a"), 4096))
|
||||||
|
raw, err := io.ReadAll(req.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
req.Body = io.NopCloser(bytes.NewReader(raw[:len(raw)-2000])) // the sender hung up mid-file
|
||||||
|
buf := &lockedBuf{}
|
||||||
|
app.Log = slog.New(slog.NewTextHandler(buf, nil))
|
||||||
|
|
||||||
|
rec := do(t, app, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, rec.Code, "a connection that drops mid-upload is the sender's to retry, not a server fault")
|
||||||
|
assert.Empty(t, sub.jobs)
|
||||||
|
assert.NotContains(t, buf.String(), "level=ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUploadPost_BidirectionalControlCharactersAreStrippedFromATitle(t *testing.T) {
|
||||||
// U+202E reverses the text that follows it, so a title could render reversed on its
|
// U+202E reverses the text that follows it, so a title could render reversed on its
|
||||||
// owner's own page (second adversarial review). Every one of the twelve is pinned.
|
// owner's own page (second adversarial review).
|
||||||
controls := []rune{0x202A, 0x202B, 0x202C, 0x202D, 0x202E, 0x2066, 0x2067, 0x2068, 0x2069, 0x200E, 0x200F, 0x061C}
|
app, sub := newUploadApp(t, audioOK, 200<<20)
|
||||||
for _, c := range controls {
|
|
||||||
t.Run(fmt.Sprintf("U+%04X", c), func(t *testing.T) {
|
|
||||||
app, sub := newUploadApp(t, audioOK, 200<<20)
|
|
||||||
|
|
||||||
rec := do(t, app, uploadReq(t, "Sprint"+string(c)+"review", "a.m4a", []byte("x")))
|
rec := do(t, app, uploadReq(t, "Sprint \u202Ereview \u2066x\u2069 \u200Fok", "a.m4a", []byte("x")))
|
||||||
|
|
||||||
require.Equal(t, http.StatusSeeOther, rec.Code)
|
require.Equal(t, http.StatusSeeOther, rec.Code)
|
||||||
var title string
|
var title string
|
||||||
require.NoError(t, rawPool(t).QueryRow(context.Background(), `SELECT title FROM uploaded_transcripts WHERE id = $1`, sub.jobs[0].UploadID).Scan(&title))
|
require.NoError(t, rawPool(t).QueryRow(context.Background(), `SELECT title FROM uploaded_transcripts WHERE id = $1`, sub.jobs[0].UploadID).Scan(&title))
|
||||||
assert.Equal(t, "Sprintreview", title, "dropped, not replaced by a space: a space would leave a visible gap")
|
for _, r := range []string{"\u202E", "\u2066", "\u2069", "\u200F"} {
|
||||||
})
|
assert.NotContains(t, title, r)
|
||||||
}
|
}
|
||||||
|
assert.Contains(t, title, "Sprint")
|
||||||
|
assert.Contains(t, title, "review")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUploadPost_SwedishLettersAndEmojiSurviveTheTitleCleaning(t *testing.T) {
|
func TestUploadPost_SwedishLettersAndEmojiSurviveTheTitleCleaning(t *testing.T) {
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
package web_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A client that hangs up in the middle of an upload is routine, not a server fault. The
|
|
||||||
// in-process recorder cannot see it (its context never cancels), so this talks to a real
|
|
||||||
// listener (third adversarial review: over a socket every such case logged an ERROR and
|
|
||||||
// half-close returned a 500, because the error page was rendered on a cancelled context).
|
|
||||||
|
|
||||||
// body positions at which the client can hang up: inside the title field, inside the file
|
|
||||||
// part's own headers, and mid-file. Every one must be handled alike (fourth adversarial
|
|
||||||
// review: the title branch had no guard and still logged an ERROR).
|
|
||||||
const (
|
|
||||||
inTitle = "in the title"
|
|
||||||
inHeaders = "in the file part's headers"
|
|
||||||
midFile = "mid-file"
|
|
||||||
)
|
|
||||||
|
|
||||||
func partialUpload(t *testing.T, addr, where string, hangUp func(*net.TCPConn)) {
|
|
||||||
t.Helper()
|
|
||||||
c, err := net.Dial("tcp", addr)
|
|
||||||
require.NoError(t, err)
|
|
||||||
tc := c.(*net.TCPConn)
|
|
||||||
const boundary = "xBOUNDARYx"
|
|
||||||
var body string
|
|
||||||
switch where {
|
|
||||||
case inTitle:
|
|
||||||
body = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"title\"\r\n\r\nhalf a tit"
|
|
||||||
case inHeaders:
|
|
||||||
body = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"file\"; filena"
|
|
||||||
default:
|
|
||||||
body = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"file\"; filename=\"a.m4a\"\r\nContent-Type: audio/mp4\r\n\r\n" + strings.Repeat("a", 4000)
|
|
||||||
}
|
|
||||||
_, err = fmt.Fprintf(tc, "POST /uploads HTTP/1.1\r\nHost: x\r\nContent-Type: multipart/form-data; boundary=%s\r\nContent-Length: 200000\r\n\r\n%s", boundary, body)
|
|
||||||
require.NoError(t, err)
|
|
||||||
time.Sleep(50 * time.Millisecond) // let the server start reading
|
|
||||||
hangUp(tc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUploadPost_AClientThatHangsUpAnywhereInTheBodyIsLoggedAtInfoAndNeverAsAnError(t *testing.T) {
|
|
||||||
hangUps := map[string]func(*net.TCPConn){
|
|
||||||
"a half close": func(c *net.TCPConn) { _ = c.CloseWrite(); time.Sleep(100 * time.Millisecond); _ = c.Close() },
|
|
||||||
"a full close": func(c *net.TCPConn) { _ = c.Close() },
|
|
||||||
"a reset (RST)": func(c *net.TCPConn) { _ = c.SetLinger(0); _ = c.Close() },
|
|
||||||
}
|
|
||||||
for _, where := range []string{inTitle, inHeaders, midFile} {
|
|
||||||
for name, hangUp := range hangUps {
|
|
||||||
t.Run(where+", "+name, func(t *testing.T) {
|
|
||||||
app, sub := newUploadApp(t, audioOK, 200<<20)
|
|
||||||
buf := &lockedBuf{}
|
|
||||||
app.Log = slog.New(slog.NewTextHandler(buf, nil))
|
|
||||||
srv := httptest.NewServer(app.Router())
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
partialUpload(t, strings.TrimPrefix(srv.URL, "http://"), where, hangUp)
|
|
||||||
|
|
||||||
require.Eventually(t, func() bool {
|
|
||||||
out := buf.String()
|
|
||||||
return strings.Contains(out, "upload abandoned") || strings.Contains(out, "upload rejected") || strings.Contains(out, "level=ERROR")
|
|
||||||
}, 3*time.Second, 20*time.Millisecond, "the server never finished handling the aborted upload")
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
out := buf.String()
|
|
||||||
assert.NotContains(t, out, "level=ERROR", "a client hanging up is not a server fault:\n%s", out)
|
|
||||||
assert.Empty(t, sub.jobs)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// goneMidAccept is an Uploader during whose Accept the client disappears: the request's
|
|
||||||
// context is cancelled, then Accept fails the way a read from a dead connection does.
|
|
||||||
type goneMidAccept struct{ cancel context.CancelFunc }
|
|
||||||
|
|
||||||
func (g goneMidAccept) Accept(context.Context, string, string, io.Reader) (string, error) {
|
|
||||||
g.cancel()
|
|
||||||
return "", fmt.Errorf("receive: %w", io.ErrClosedPipe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUploadPost_AnAbandonedRequestGetsA499NotAnImplicitEmpty200(t *testing.T) {
|
|
||||||
// If the context is cancelled but the connection can still be written to (a client that
|
|
||||||
// half-closed after sending), a missing status becomes 200 with an empty body: a false
|
|
||||||
// success (fourth adversarial review).
|
|
||||||
app, _ := newUploadApp(t, audioOK, 200<<20)
|
|
||||||
buf := &lockedBuf{}
|
|
||||||
app.Log = slog.New(slog.NewTextHandler(buf, nil))
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
app.Uploads = goneMidAccept{cancel: cancel}
|
|
||||||
|
|
||||||
rec := do(t, app, uploadReq(t, "t", "a.m4a", []byte("x")).WithContext(ctx))
|
|
||||||
|
|
||||||
assert.Equal(t, 499, rec.Code, "never an implicit 200 for an upload that was not accepted")
|
|
||||||
assert.Contains(t, buf.String(), "upload abandoned")
|
|
||||||
assert.NotContains(t, buf.String(), "level=ERROR")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUploadPost_ADatabaseDropThatLooksLikeAnEOFIsStillAServerError(t *testing.T) {
|
|
||||||
// pgx turns a connection that drops mid-message into io.ErrUnexpectedEOF. Mapping that
|
|
||||||
// sentinel to "your upload was cut off" (a 400) would hide a real fault: only an error
|
|
||||||
// from reading the CLIENT's body is theirs (third adversarial review).
|
|
||||||
app, _ := newUploadApp(t, audioOK, 200<<20)
|
|
||||||
buf := &lockedBuf{}
|
|
||||||
app.Log = slog.New(slog.NewTextHandler(buf, nil))
|
|
||||||
app.Uploads = failingUploader{err: fmt.Errorf("failed to receive message: %w", io.ErrUnexpectedEOF)}
|
|
||||||
|
|
||||||
rec := do(t, app, uploadReq(t, "t", "a.m4a", []byte("x")))
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusInternalServerError, rec.Code)
|
|
||||||
assert.Contains(t, buf.String(), "level=ERROR")
|
|
||||||
}
|
|
||||||
@@ -252,7 +252,7 @@ func TestUploadProgress_TheFullPageCarriesTheVerySameProgressListAsTheFragment(t
|
|||||||
"the page a user opens and the fragment HTMX swaps in must show the same list, or the display jumps on the first poll")
|
"the page a user opens and the fragment HTMX swaps in must show the same list, or the display jumps on the first poll")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUploadProgress_AStepNoteIsShownDirectlyUnderItsStepAndEscaped(t *testing.T) {
|
func TestUploadProgress_AStepNoteIsShownUnderItsStepAndEscaped(t *testing.T) {
|
||||||
app, _ := newUploadApp(t, audioOK, 200<<20)
|
app, _ := newUploadApp(t, audioOK, 200<<20)
|
||||||
fixedClock(app)
|
fixedClock(app)
|
||||||
id := seedUpload(t, userID, "m", "transcribing", "")
|
id := seedUpload(t, userID, "m", "transcribing", "")
|
||||||
@@ -262,26 +262,7 @@ func TestUploadProgress_AStepNoteIsShownDirectlyUnderItsStepAndEscaped(t *testin
|
|||||||
|
|
||||||
b := statusBody(t, app, id)
|
b := statusBody(t, app, id)
|
||||||
|
|
||||||
audio := strings.Index(b, "Preparing the audio")
|
assert.Contains(t, b, "The audio is 20 min long")
|
||||||
note := strings.Index(b, "The audio is 20 min long")
|
|
||||||
next := strings.Index(b, "Transcribing with iguana/pianissimo")
|
|
||||||
require.True(t, audio >= 0 && note > audio && next > note, "the note must sit between its own step and the next one:\n%s", b)
|
|
||||||
assert.Contains(t, b, "step-note")
|
assert.Contains(t, b, "step-note")
|
||||||
assert.NotContains(t, b, "<b>cut off</b>", "a note is words, never markup")
|
assert.NotContains(t, b, "<b>cut off</b>", "a note is words, never markup")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUploadProgress_ADonePageShowsItsRecordOpenWhenAStepHasANote(t *testing.T) {
|
|
||||||
// A truncated recording must not look like a clean one: the note that replaced the
|
|
||||||
// refusal is the only warning, so it cannot sit inside a collapsed list (third review).
|
|
||||||
app, _ := newUploadApp(t, audioOK, 200<<20)
|
|
||||||
fixedClock(app)
|
|
||||||
noted := seedUpload(t, userID, "noted", "done", "Synthetic transcript text.")
|
|
||||||
steps := []domain.ProgressStep{step("audio", "Preparing the audio", domain.StepDone, 20*time.Minute, 40*time.Second, 0, 0)}
|
|
||||||
steps[0].Note = "The audio is 20 min long; the file claimed 1 h 19 min."
|
|
||||||
seedProgress(t, noted, steps, progressNow.Add(-time.Minute), progressNow.Add(-time.Minute))
|
|
||||||
clean := seedUpload(t, userID, "clean", "done", "Synthetic transcript text.")
|
|
||||||
seedProgress(t, clean, []domain.ProgressStep{step("audio", "Preparing the audio", domain.StepDone, 20*time.Minute, 40*time.Second, 0, 0)}, progressNow.Add(-time.Minute), progressNow.Add(-time.Minute))
|
|
||||||
|
|
||||||
assert.Contains(t, statusBody(t, app, noted), `<details class="what-was-done" open>`)
|
|
||||||
assert.NotContains(t, statusBody(t, app, clean), `<details class="what-was-done" open>`, "a clean record stays collapsed")
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -312,17 +312,7 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if len(u.Progress) > 0 {
|
if len(u.Progress) > 0 {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<details class=\"what-was-done\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<details class=\"what-was-done\"><summary>What was done</summary>")
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
if hasNote(u.Progress) {
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, " open")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
|
||||||
return templ_7745c5c3_Err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "><summary>What was done</summary>")
|
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -330,13 +320,13 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</details> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</details> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if chatEnabled {
|
if chatEnabled {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<p><a class=\"btn\" href=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<p><a class=\"btn\" href=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -349,12 +339,12 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\">Ask about this recording</a></p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\">Ask about this recording</a></p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<details open><summary>Transcript</summary><pre class=\"transcript\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<details open><summary>Transcript</summary><pre class=\"transcript\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -367,12 +357,12 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</pre></details></div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</pre></details></div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
case domain.UploadFailed:
|
case domain.UploadFailed:
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<div class=\"upload-status upload-failed flash flash-error\" role=\"alert\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<div class=\"upload-status upload-failed flash flash-error\" role=\"alert\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -380,7 +370,7 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -393,12 +383,12 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if len(u.Progress) > 0 {
|
if len(u.Progress) > 0 {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<details class=\"what-was-done\" open><summary>Where it stopped</summary>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<details class=\"what-was-done\" open><summary>Where it stopped</summary>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -406,17 +396,17 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</details>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</details>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<div class=\"upload-status upload-working\" role=\"status\" hx-get=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<div class=\"upload-status upload-working\" role=\"status\" hx-get=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -429,7 +419,7 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\" hx-trigger=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\" hx-trigger=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -442,7 +432,7 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\" hx-swap=\"outerHTML\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" hx-swap=\"outerHTML\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -457,7 +447,7 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, " ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, " ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -466,18 +456,18 @@ func uploadStatus(u domain.UploadedTranscript, chatEnabled bool, now time.Time)
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<p>Transcribing… a long recording takes a few minutes.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<p>Transcribing… a long recording takes a few minutes.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<p>Queued — waiting for its turn to be transcribed.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<p>Queued — waiting for its turn to be transcribed.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -521,7 +511,7 @@ func UploadChatPage(u domain.UploadedTranscript, v chatView) templ.Component {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
ctx = templ.InitializeContext(ctx)
|
ctx = templ.InitializeContext(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<article class=\"account\"><h1>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<article class=\"account\"><h1>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -534,7 +524,7 @@ func UploadChatPage(u domain.UploadedTranscript, v chatView) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</h1><section id=\"chat-section\" class=\"chat-dock chat-dock-open\"><h2 class=\"chat-heading\">Ask about this recording</h2><p class=\"chat-scope muted\">Answers come only from this recording's transcript, which is private to you.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</h1><section id=\"chat-section\" class=\"chat-dock chat-dock-open\"><h2 class=\"chat-heading\">Ask about this recording</h2><p class=\"chat-scope muted\">Answers come only from this recording's transcript, which is private to you.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -542,7 +532,7 @@ func UploadChatPage(u domain.UploadedTranscript, v chatView) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</section><p><a href=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</section><p><a href=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -555,7 +545,7 @@ func UploadChatPage(u domain.UploadedTranscript, v chatView) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "\">Back to the transcript</a></p></article>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "\">Back to the transcript</a></p></article>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -595,7 +585,7 @@ func videoNotice(hadVideo bool) templ.Component {
|
|||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
if hadVideo {
|
if hadVideo {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "<p class=\"muted video-notice\">This file contained a video track. Only its audio was used; the video was discarded and nothing from it was kept.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<p class=\"muted video-notice\">This file contained a video track. Only its audio was used; the video was discarded and nothing from it was kept.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -632,7 +622,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if r != nil {
|
if r != nil {
|
||||||
switch r.Status {
|
switch r.Status {
|
||||||
case domain.ReconcileDone:
|
case domain.ReconcileDone:
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<div class=\"reconciliation\"><p>Reconciled from two transcripts (")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<div class=\"reconciliation\"><p>Reconciled from two transcripts (")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -645,7 +635,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, " and ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, " and ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -658,7 +648,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "): ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "): ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -671,7 +661,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " differences, ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, " differences, ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -684,7 +674,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, " settled automatically, ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, " settled automatically, ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -697,7 +687,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, " decided by the model (")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, " decided by the model (")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -710,7 +700,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "), ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "), ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -723,17 +713,17 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, " uncertain.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, " uncertain.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if len(shownSpots(r)) > 0 {
|
if len(shownSpots(r)) > 0 {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<details><summary>Spots to check against the audio</summary><ul>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<details><summary>Spots to check against the audio</summary><ul>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
for _, c := range shownSpots(r) {
|
for _, c := range shownSpots(r) {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<li>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "<li>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -746,12 +736,12 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, ": ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, ": ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if c.Before != "" {
|
if c.Before != "" {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<span class=\"muted\">…")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<span class=\"muted\">…")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -764,7 +754,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</span> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</span> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -778,7 +768,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, " / ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, " / ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -791,12 +781,12 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, " — ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, " — ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if c.Unsure {
|
if c.Unsure {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<span class=\"chip chip-warn\">uncertain</span> kept ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "<span class=\"chip chip-warn\">uncertain</span> kept ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -810,7 +800,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<span class=\"chip\">changed</span> used ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<span class=\"chip\">changed</span> used ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -824,17 +814,17 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</li>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</li>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</ul>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</ul>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if moreSpots(r) > 0 {
|
if moreSpots(r) > 0 {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<p class=\"muted\">and ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "<p class=\"muted\">and ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -847,22 +837,22 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, " more not listed</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, " more not listed</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "</details>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</details>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</div>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</div>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
case domain.ReconcileSingle:
|
case domain.ReconcileSingle:
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<p class=\"muted reconciliation\">Only one engine produced this transcript (")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<p class=\"muted reconciliation\">Only one engine produced this transcript (")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -875,12 +865,12 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "), so there was no second opinion to reconcile it with.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "), so there was no second opinion to reconcile it with.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
case domain.ReconcileSkipped:
|
case domain.ReconcileSkipped:
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "<p class=\"muted reconciliation\">Two transcripts were made but could not be reconciled (")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<p class=\"muted reconciliation\">Two transcripts were made but could not be reconciled (")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -893,7 +883,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "); the text shown is ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "); the text shown is ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -906,7 +896,7 @@ func reconciliationNote(r *domain.Reconciliation) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "'s, unreconciled.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "'s, unreconciled.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -942,7 +932,7 @@ func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.
|
|||||||
templ_7745c5c3_Var43 = templ.NopComponent
|
templ_7745c5c3_Var43 = templ.NopComponent
|
||||||
}
|
}
|
||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "<ol class=\"steps\" aria-label=\"Progress\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "<ol class=\"steps\" aria-label=\"Progress\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -952,7 +942,7 @@ func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<li class=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<li class=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -965,32 +955,32 @@ func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if s.State == domain.StepRunning {
|
if s.State == domain.StepRunning {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, " aria-current=\"step\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, " aria-current=\"step\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, ">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, ">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if s.State == domain.StepRunning && live {
|
if s.State == domain.StepRunning && live {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "<span class=\"step-mark\" aria-hidden=\"true\"><span class=\"spinner\"></span></span> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "<span class=\"step-mark\" aria-hidden=\"true\"><span class=\"spinner\"></span></span> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
} else if s.State == domain.StepRunning {
|
} else if s.State == domain.StepRunning {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "<span class=\"step-mark step-mark-warn\" aria-hidden=\"true\">!</span> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<span class=\"step-mark step-mark-warn\" aria-hidden=\"true\">!</span> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "<span class=\"step-mark\" aria-hidden=\"true\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "<span class=\"step-mark\" aria-hidden=\"true\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1003,12 +993,12 @@ func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "</span> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "</span> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "<span class=\"step-label\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "<span class=\"step-label\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1021,12 +1011,12 @@ func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "</span> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "</span> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
if t := stepTime(s, now); t != "" {
|
if t := stepTime(s, now); t != "" {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<span class=\"step-time\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "<span class=\"step-time\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1039,13 +1029,13 @@ func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "</span> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "</span> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.Note != "" {
|
if s.Note != "" {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "<span class=\"step-note\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<span class=\"step-note\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1058,13 +1048,13 @@ func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "</span> ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "</span> ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if s.State == domain.StepRunning && s.Total > 0 {
|
if s.State == domain.StepRunning && s.Total > 0 {
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<progress class=\"step-bar\" value=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "<progress class=\"step-bar\" value=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1077,7 +1067,7 @@ func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "\" max=\"")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "\" max=\"")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1090,7 +1080,7 @@ func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "\"></progress> <span class=\"step-count\">")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "\"></progress> <span class=\"step-count\">")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1103,7 +1093,7 @@ func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, " of ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, " of ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1116,17 +1106,17 @@ func progressSteps(steps []domain.ProgressStep, now time.Time, live bool) templ.
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</span>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</span>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "</li>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "</li>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "</ol>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</ol>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1159,7 +1149,7 @@ func healthNote(h jobHealth, silent time.Duration) templ.Component {
|
|||||||
ctx = templ.ClearChildren(ctx)
|
ctx = templ.ClearChildren(ctx)
|
||||||
switch h {
|
switch h {
|
||||||
case healthLost:
|
case healthLost:
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "<p class=\"step-health step-health-lost\" role=\"alert\">The service stopped reporting ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "<p class=\"step-health step-health-lost\" role=\"alert\">The service stopped reporting ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1172,12 +1162,12 @@ func healthNote(h jobHealth, silent time.Duration) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, " ago. It may have restarted or crashed. This page keeps checking; if nothing changes, upload the recording again.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, " ago. It may have restarted or crashed. This page keeps checking; if nothing changes, upload the recording again.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
case healthQuiet:
|
case healthQuiet:
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "<p class=\"step-health step-health-quiet\">Nothing new for ")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "<p class=\"step-health step-health-quiet\">Nothing new for ")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
@@ -1190,7 +1180,7 @@ func healthNote(h jobHealth, silent time.Duration) templ.Component {
|
|||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, ": the model may be slow. The service is still running, and the job gives up on its own if it takes too long.</p>")
|
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, ": the model may be slow. The service is still running, and the job gives up on its own if it takes too long.</p>")
|
||||||
if templ_7745c5c3_Err != nil {
|
if templ_7745c5c3_Err != nil {
|
||||||
return templ_7745c5c3_Err
|
return templ_7745c5c3_Err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user