diff --git a/internal/web/uploads.go b/internal/web/uploads.go index 836b493..921a42f 100644 --- a/internal/web/uploads.go +++ b/internal/web/uploads.go @@ -275,20 +275,24 @@ func titleFromFilename(name string) string { // 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 word of it that is not that format's own -// connector is judged as an alias, so a composite of local models is local, and anything -// else (a cloud alias, one with no provider prefix, one with a space in it) is not. +// " + , reconciled by " (ensemble.go). That shape is taken apart exactly +// and each alias is judged whole, so a composite of local models is local, and anything +// else (a cloud alias, one with no provider prefix, one with a space or a connector word in +// it) is not. A string that is not that shape is one alias. func cloudProvider(endpoint string) (string, bool) { - for _, tok := range strings.Fields(endpoint) { - tok = strings.Trim(tok, ",") - switch tok { - case "", "+", "reconciled", "by": - continue - } - if !config.IsLocalModel(tok) { - provider, _, _ := strings.Cut(tok, "/") - if provider == "" { - provider = tok + endpoint = strings.TrimSpace(endpoint) + if endpoint == "" { + return "", false + } + aliases := []string{endpoint} + if engines, reconciler, ok := strings.Cut(endpoint, ", reconciled by "); ok { + aliases = append(strings.Split(engines, " + "), reconciler) + } + for _, a := range aliases { + if a = strings.TrimSpace(a); !config.IsLocalModel(a) { + provider, _, _ := strings.Cut(a, "/") + if provider = strings.TrimSpace(provider); provider == "" { + provider = a } return provider, true } diff --git a/internal/web/uploads_cloud_note_test.go b/internal/web/uploads_cloud_note_test.go index f703e76..82fcd42 100644 --- a/internal/web/uploads_cloud_note_test.go +++ b/internal/web/uploads_cloud_note_test.go @@ -103,3 +103,38 @@ func TestUploadStatus_AReconciledEndpointWithAnUnprefixedAliasIsNotCalledLocal(t }) } } + +// "by", "reconciled" and "+" are connectors only inside the composite format. As a whole +// endpoint, or as an alias within the composite, they are model names and are judged. +func TestUploadStatus_AConnectorWordUsedAsAModelNameIsStillJudged(t *testing.T) { + for _, endpoint := range []string{ + "by", + "reconciled", + "+", + "iguana/kb-whisper + by, reconciled by iguana/gemma4-26b", + "iguana/kb-whisper + reconciled, reconciled by iguana/gemma4-26b", + "iguana/kb-whisper + +, reconciled by iguana/gemma4-26b", + "/x", // no provider at all: the note must still name something + } { + 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.Contains(t, b, "left the homelab") + assert.NotContains(t, b, "cloud service ()") + }) + } +} + +func TestUploadStatus_AReconcilerThatIsNotLocalNamesTheCloudProvider(t *testing.T) { + app, _ := newUploadApp(t, audioOK, 200<<20) + id := seedUpload(t, userID, "m", "done", "Synthetic transcript text.") + setEndpoint(t, id, "iguana/kb-whisper + koala/whisper, reconciled by berget/mistral-large") + + b := body(t, do(t, app, get("/uploads/"+id+"/status"))) + + assert.Contains(t, b, "cloud service (berget)") +}