diff --git a/app/ui/app/src/utils/mergeModels.ts b/app/ui/app/src/utils/mergeModels.ts index 814d2af42..861190b10 100644 --- a/app/ui/app/src/utils/mergeModels.ts +++ b/app/ui/app/src/utils/mergeModels.ts @@ -2,27 +2,28 @@ import { Model } from "@/gotypes"; // Featured models list (in priority order) export const FEATURED_MODELS = [ + "kimi-k2.5:cloud", + "glm-5:cloud", + "minimax-m2.7:cloud", + "gemma4:31b-cloud", + "qwen3.5:397b-cloud", "gpt-oss:120b-cloud", "gpt-oss:20b-cloud", "deepseek-v3.1:671b-cloud", - "qwen3-coder:480b-cloud", - "qwen3-vl:235b-cloud", - "minimax-m2:cloud", - "glm-4.6:cloud", "gpt-oss:120b", "gpt-oss:20b", - "gemma3:27b", - "gemma3:12b", - "gemma3:4b", - "gemma3:1b", + "gemma4:31b", + "gemma4:26b", + "gemma4:e4b", + "gemma4:e2b", "deepseek-r1:8b", "qwen3-coder:30b", "qwen3-vl:30b", "qwen3-vl:8b", "qwen3-vl:4b", - "qwen3:30b", - "qwen3:8b", - "qwen3:4b", + "qwen3.5:27b", + "qwen3.5:9b", + "qwen3.5:4b", ]; function alphabeticalSort(a: Model, b: Model): number { diff --git a/cmd/launch/integrations_test.go b/cmd/launch/integrations_test.go index 4cabd654b..2460993dc 100644 --- a/cmd/launch/integrations_test.go +++ b/cmd/launch/integrations_test.go @@ -310,7 +310,7 @@ func names(items []ModelItem) []string { func TestBuildModelList_NoExistingModels(t *testing.T) { items, _, _, _ := buildModelList(nil, nil, "") - want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "glm-4.7-flash", "qwen3.5"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "gemma4", "qwen3.5"} if diff := cmp.Diff(want, names(items)); diff != "" { t.Errorf("with no existing models, items should be recommended in order (-want +got):\n%s", diff) } @@ -338,7 +338,7 @@ func TestBuildModelList_OnlyLocalModels_CloudRecsAtBottom(t *testing.T) { got := names(items) // Recommended pinned at top (local recs first, then cloud recs when only-local), then installed non-recs - want := []string{"glm-4.7-flash", "qwen3.5", "kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "llama3.2", "qwen2.5"} + want := []string{"gemma4", "qwen3.5", "kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "llama3.2", "qwen2.5"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("recs pinned at top, local recs before cloud recs (-want +got):\n%s", diff) } @@ -354,7 +354,7 @@ func TestBuildModelList_BothCloudAndLocal_RegularSort(t *testing.T) { got := names(items) // All recs pinned at top (cloud before local in mixed case), then non-recs - want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "glm-4.7-flash", "qwen3.5", "llama3.2"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "gemma4", "qwen3.5", "llama3.2"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("recs pinned at top, cloud recs first in mixed case (-want +got):\n%s", diff) } @@ -376,7 +376,7 @@ func TestBuildModelList_PreCheckedFirst(t *testing.T) { func TestBuildModelList_ExistingRecommendedMarked(t *testing.T) { existing := []modelInfo{ - {Name: "glm-4.7-flash", Remote: false}, + {Name: "gemma4", Remote: false}, {Name: "glm-5:cloud", Remote: true}, } @@ -384,7 +384,7 @@ func TestBuildModelList_ExistingRecommendedMarked(t *testing.T) { for _, item := range items { switch item.Name { - case "glm-4.7-flash", "glm-5:cloud": + case "gemma4", "glm-5:cloud": if strings.HasSuffix(item.Description, "(not downloaded)") { t.Errorf("installed recommended %q should not have '(not downloaded)' suffix, got %q", item.Name, item.Description) } @@ -402,17 +402,17 @@ func TestBuildModelList_ExistingRecommendedMarked(t *testing.T) { func TestBuildModelList_ExistingCloudModelsNotPushedToBottom(t *testing.T) { existing := []modelInfo{ - {Name: "glm-4.7-flash", Remote: false}, + {Name: "gemma4", Remote: false}, {Name: "glm-5:cloud", Remote: true}, } items, _, _, _ := buildModelList(existing, nil, "") got := names(items) - // glm-4.7-flash and glm-5:cloud are installed so they sort normally; + // gemma4 and glm-5:cloud are installed so they sort normally; // kimi-k2.5:cloud, qwen3.5:cloud, and qwen3.5 are not installed so they go to the bottom // All recs: cloud first in mixed case, then local, in rec order within each - want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "glm-4.7-flash", "qwen3.5"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "gemma4", "qwen3.5"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("all recs, cloud first in mixed case (-want +got):\n%s", diff) } @@ -430,7 +430,7 @@ func TestBuildModelList_HasRecommendedCloudModel_OnlyNonInstalledAtBottom(t *tes // kimi-k2.5:cloud is installed so it sorts normally; // the rest of the recommendations are not installed so they go to the bottom // All recs pinned at top (cloud first in mixed case), then non-recs - want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "glm-4.7-flash", "qwen3.5", "llama3.2"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "gemma4", "qwen3.5", "llama3.2"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("recs pinned at top, cloud first in mixed case (-want +got):\n%s", diff) } @@ -452,7 +452,7 @@ func TestBuildModelList_HasRecommendedCloudModel_OnlyNonInstalledAtBottom(t *tes func TestBuildModelList_LatestTagStripped(t *testing.T) { existing := []modelInfo{ - {Name: "glm-4.7-flash:latest", Remote: false}, + {Name: "gemma4:latest", Remote: false}, {Name: "llama3.2:latest", Remote: false}, } @@ -466,20 +466,20 @@ func TestBuildModelList_LatestTagStripped(t *testing.T) { } } - // glm-4.7-flash should not be duplicated (existing :latest matches the recommendation) + // gemma4 should not be duplicated (existing :latest matches the recommendation) count := 0 for _, name := range got { - if name == "glm-4.7-flash" { + if name == "gemma4" { count++ } } if count != 1 { - t.Errorf("glm-4.7-flash should appear exactly once, got %d in %v", count, got) + t.Errorf("gemma4 should appear exactly once, got %d in %v", count, got) } // Stripped name should be in existingModels so it won't be pulled - if !existingModels["glm-4.7-flash"] { - t.Error("glm-4.7-flash should be in existingModels") + if !existingModels["gemma4"] { + t.Error("gemma4 should be in existingModels") } } @@ -497,8 +497,8 @@ func TestBuildModelList_ReturnsExistingAndCloudMaps(t *testing.T) { if !existingModels["glm-5:cloud"] { t.Error("glm-5:cloud should be in existingModels") } - if existingModels["glm-4.7-flash"] { - t.Error("glm-4.7-flash should not be in existingModels (it's a recommendation)") + if existingModels["gemma4"] { + t.Error("gemma4 should not be in existingModels (it's a recommendation)") } if !cloudModels["glm-5:cloud"] { @@ -517,7 +517,7 @@ func TestBuildModelList_ReturnsExistingAndCloudMaps(t *testing.T) { func TestBuildModelList_RecommendedFieldSet(t *testing.T) { existing := []modelInfo{ - {Name: "glm-4.7-flash", Remote: false}, + {Name: "gemma4", Remote: false}, {Name: "llama3.2:latest", Remote: false}, } @@ -525,7 +525,7 @@ func TestBuildModelList_RecommendedFieldSet(t *testing.T) { for _, item := range items { switch item.Name { - case "glm-4.7-flash", "qwen3.5", "glm-5:cloud", "kimi-k2.5:cloud", "qwen3.5:cloud": + case "gemma4", "qwen3.5", "glm-5:cloud", "kimi-k2.5:cloud", "qwen3.5:cloud": if !item.Recommended { t.Errorf("%q should have Recommended=true", item.Name) } @@ -548,7 +548,7 @@ func TestBuildModelList_MixedCase_CloudRecsFirst(t *testing.T) { // Cloud recs should sort before local recs in mixed case cloudIdx := slices.Index(got, "glm-5:cloud") - localIdx := slices.Index(got, "glm-4.7-flash") + localIdx := slices.Index(got, "gemma4") if cloudIdx > localIdx { t.Errorf("cloud recs should be before local recs in mixed case, got %v", got) } @@ -563,7 +563,7 @@ func TestBuildModelList_OnlyLocal_LocalRecsFirst(t *testing.T) { got := names(items) // Local recs should sort before cloud recs in only-local case - localIdx := slices.Index(got, "glm-4.7-flash") + localIdx := slices.Index(got, "gemma4") cloudIdx := slices.Index(got, "glm-5:cloud") if localIdx > cloudIdx { t.Errorf("local recs should be before cloud recs in only-local case, got %v", got) @@ -583,7 +583,7 @@ func TestBuildModelList_RecsAboveNonRecs(t *testing.T) { lastRecIdx := -1 firstNonRecIdx := len(got) for i, name := range got { - isRec := name == "glm-4.7-flash" || name == "qwen3.5" || name == "minimax-m2.7:cloud" || name == "glm-5:cloud" || name == "kimi-k2.5:cloud" || name == "qwen3.5:cloud" + isRec := name == "gemma4" || name == "qwen3.5" || name == "minimax-m2.7:cloud" || name == "glm-5:cloud" || name == "kimi-k2.5:cloud" || name == "qwen3.5:cloud" if isRec && i > lastRecIdx { lastRecIdx = i } diff --git a/cmd/launch/launch_test.go b/cmd/launch/launch_test.go index 98c64cf91..cc072765c 100644 --- a/cmd/launch/launch_test.go +++ b/cmd/launch/launch_test.go @@ -521,7 +521,7 @@ func TestResolveRunModel_ForcePicker_DoesNotReorderByLastModel(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/api/tags": - fmt.Fprint(w, `{"models":[{"name":"qwen3.5"},{"name":"glm-4.7-flash"}]}`) + fmt.Fprint(w, `{"models":[{"name":"qwen3.5"},{"name":"gemma4"}]}`) case "/api/show": fmt.Fprint(w, `{"model":"qwen3.5"}`) default: @@ -540,7 +540,7 @@ func TestResolveRunModel_ForcePicker_DoesNotReorderByLastModel(t *testing.T) { t.Fatal("expected selector to receive model items") } - glmIdx := slices.Index(gotNames, "glm-4.7-flash") + glmIdx := slices.Index(gotNames, "gemma4") qwenIdx := slices.Index(gotNames, "qwen3.5") if glmIdx == -1 || qwenIdx == -1 { t.Fatalf("expected recommended local models in selector items, got %v", gotNames) diff --git a/cmd/launch/models.go b/cmd/launch/models.go index d541eb4ae..ad1c16b9b 100644 --- a/cmd/launch/models.go +++ b/cmd/launch/models.go @@ -25,13 +25,13 @@ var recommendedModels = []ModelItem{ {Name: "qwen3.5:cloud", Description: "Reasoning, coding, and agentic tool use with vision", Recommended: true}, {Name: "glm-5:cloud", Description: "Reasoning and code generation", Recommended: true}, {Name: "minimax-m2.7:cloud", Description: "Fast, efficient coding and real-world productivity", Recommended: true}, - {Name: "glm-4.7-flash", Description: "Reasoning and code generation locally", Recommended: true}, + {Name: "gemma4", Description: "Reasoning and code generation locally", Recommended: true}, {Name: "qwen3.5", Description: "Reasoning, coding, and visual understanding locally", Recommended: true}, } var recommendedVRAM = map[string]string{ - "glm-4.7-flash": "~25GB", - "qwen3.5": "~11GB", + "gemma4": "~16GB", + "qwen3.5": "~11GB", } // cloudModelLimit holds context and output token limits for a cloud model.