diff --git a/internal/web/uploads.go b/internal/web/uploads.go index f4638f6..8ef6a43 100644 --- a/internal/web/uploads.go +++ b/internal/web/uploads.go @@ -273,12 +273,30 @@ func titleFromFilename(name string) string { // cloudProvider names the cloud service that produced a transcript, or false when it was // served locally (or by an unknown endpoint). "Local" is config.IsLocalModel: the same // definition the start-up validation uses, not a second string match here (#52). +// +// The endpoint is one alias, except for the two-engine path (ADR-034), which writes +// " + , reconciled by ". Every alias in it is judged on its own, so a +// composite of local models is local and one naming a cloud alias names that provider. func cloudProvider(endpoint string) (string, bool) { - if endpoint == "" || config.IsLocalModel(endpoint) { + if strings.TrimSpace(endpoint) == "" { return "", false } - provider, _, _ := strings.Cut(endpoint, "/") - return provider, true + aliases := []string{} + for _, tok := range strings.Fields(endpoint) { + if tok = strings.Trim(tok, ","); strings.Contains(tok, "/") { + aliases = append(aliases, tok) + } + } + if len(aliases) == 0 { + aliases = append(aliases, strings.TrimSpace(endpoint)) + } + for _, a := range aliases { + if !config.IsLocalModel(a) { + provider, _, _ := strings.Cut(a, "/") + return provider, true + } + } + return "", false } // hasNote reports whether any phase carries a note. A note is the only warning for a diff --git a/internal/web/uploads_cloud_note_test.go b/internal/web/uploads_cloud_note_test.go index f6ebf76..7e68187 100644 --- a/internal/web/uploads_cloud_note_test.go +++ b/internal/web/uploads_cloud_note_test.go @@ -53,3 +53,32 @@ func TestUploadStatus_AnEndpointThatOnlyLooksLocalIsStillNamedAsCloud(t *testing assert.Contains(t, b, "left the homelab") } + +// The two-engine path (ADR-034) writes one composite endpoint string. Every name in it is +// a local model, so the audio never left the homelab and the note must not say it did. +func TestUploadStatus_AReconciledAllLocalEndpointCarriesNoCloudNote(t *testing.T) { + for _, endpoint := range []string{ + "iguana/kb-whisper + koala/whisper, reconciled by iguana/gemma4-26b", + "iguana/kb-whisper + koala/whisper, reconciled by iguana/gemma4-26b\n", + } { + t.Run(endpoint, func(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript text.") + setEndpoint(t, id, endpoint) + + b := body(t, do(t, app, get("/uploads/"+id+"/status"))) + + assert.NotContains(t, b, "left the homelab") + }) + } +} + +func TestUploadStatus_AReconciledEndpointNamingACloudEngineStillNamesTheCloudProvider(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript text.") + setEndpoint(t, id, "iguana/kb-whisper + berget/kb-whisper, reconciled by iguana/gemma4-26b") + + b := body(t, do(t, app, get("/uploads/"+id+"/status"))) + + assert.Contains(t, b, "cloud service (berget)") +}