mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
feat: add enabled toggle to chat model admin panel (#23665)
Adds an `enabled` toggle to the chat model admin create/edit form so admins can disable a model without soft-deleting it. Disabled models stay visible in admin settings but stop appearing in user-facing model selectors. The backend already supported this (`chat_model_configs.enabled` column, filtered queries, and SDK fields). This change wires it into the admin UI and adds coverage on both sides. **Backend:** three new subtests in `coderd/exp_chats_test.go` verifying the visibility contract (admin sees disabled models, non-admin doesn't, update-to-disabled preserves the record). **Frontend:** `enabled` field added to form logic and seeded from the existing model (defaults to `true` for new models). A Switch+Tooltip control renders in the form header, matching the MCP Server panel pattern. Two interaction stories cover the create-disabled and toggle-existing flows.
This commit is contained in:
@@ -1226,6 +1226,73 @@ func TestListChatModelConfigs(t *testing.T) {
|
||||
require.True(t, found)
|
||||
})
|
||||
|
||||
t.Run("AdminIncludesDisabledModelConfigs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
client := newChatClient(t)
|
||||
_ = coderdtest.CreateFirstUser(t, client.Client)
|
||||
|
||||
_, err := client.CreateChatProvider(ctx, codersdk.CreateChatProviderConfigRequest{
|
||||
Provider: "openai",
|
||||
APIKey: "test-api-key",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
contextLimit := int64(4096)
|
||||
enabled := false
|
||||
disabledConfig, err := client.CreateChatModelConfig(ctx, codersdk.CreateChatModelConfigRequest{
|
||||
Provider: "openai",
|
||||
Model: "gpt-4o-disabled",
|
||||
DisplayName: "GPT-4o Disabled",
|
||||
Enabled: &enabled,
|
||||
ContextLimit: &contextLimit,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.False(t, disabledConfig.Enabled)
|
||||
|
||||
configs, err := client.ListChatModelConfigs(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
found := false
|
||||
for _, config := range configs {
|
||||
if config.ID == disabledConfig.ID {
|
||||
found = true
|
||||
require.False(t, config.Enabled)
|
||||
require.Equal(t, disabledConfig.DisplayName, config.DisplayName)
|
||||
}
|
||||
}
|
||||
require.True(t, found)
|
||||
})
|
||||
|
||||
t.Run("NonAdminExcludesDisabledModelConfigs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
adminClient := newChatClient(t)
|
||||
firstUser := coderdtest.CreateFirstUser(t, adminClient.Client)
|
||||
enabledConfig := createChatModelConfig(t, adminClient)
|
||||
memberClientRaw, _ := coderdtest.CreateAnotherUser(t, adminClient.Client, firstUser.OrganizationID)
|
||||
memberClient := codersdk.NewExperimentalClient(memberClientRaw)
|
||||
|
||||
contextLimit := int64(4096)
|
||||
enabled := false
|
||||
_, err := adminClient.CreateChatModelConfig(ctx, codersdk.CreateChatModelConfigRequest{
|
||||
Provider: "openai",
|
||||
Model: "gpt-4o-disabled",
|
||||
DisplayName: "GPT-4o Disabled",
|
||||
Enabled: &enabled,
|
||||
ContextLimit: &contextLimit,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
configs, err := memberClient.ListChatModelConfigs(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, configs, 1)
|
||||
require.Equal(t, enabledConfig.ID, configs[0].ID)
|
||||
require.True(t, configs[0].Enabled)
|
||||
})
|
||||
|
||||
t.Run("DeserializesLegacyPricingJSON", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1469,6 +1536,102 @@ func TestUpdateChatModelConfig(t *testing.T) {
|
||||
requireChatModelPricing(t, configs[0].ModelConfig, pricing)
|
||||
})
|
||||
|
||||
t.Run("DisablePreservesRecordAndHidesItFromNonAdmins", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
adminClient := newChatClient(t)
|
||||
firstUser := coderdtest.CreateFirstUser(t, adminClient.Client)
|
||||
memberClientRaw, _ := coderdtest.CreateAnotherUser(t, adminClient.Client, firstUser.OrganizationID)
|
||||
memberClient := codersdk.NewExperimentalClient(memberClientRaw)
|
||||
modelConfig := createChatModelConfig(t, adminClient)
|
||||
|
||||
enabled := false
|
||||
updated, err := adminClient.UpdateChatModelConfig(ctx, modelConfig.ID, codersdk.UpdateChatModelConfigRequest{
|
||||
Enabled: &enabled,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, modelConfig.ID, updated.ID)
|
||||
require.False(t, updated.Enabled)
|
||||
|
||||
adminConfigs, err := adminClient.ListChatModelConfigs(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
foundForAdmin := false
|
||||
for _, config := range adminConfigs {
|
||||
if config.ID == modelConfig.ID {
|
||||
foundForAdmin = true
|
||||
require.False(t, config.Enabled)
|
||||
}
|
||||
}
|
||||
require.True(t, foundForAdmin)
|
||||
|
||||
memberConfigs, err := memberClient.ListChatModelConfigs(ctx)
|
||||
require.NoError(t, err)
|
||||
for _, config := range memberConfigs {
|
||||
require.NotEqual(t, modelConfig.ID, config.ID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ReEnableRestoresVisibilityForNonAdmins", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
adminClient := newChatClient(t)
|
||||
firstUser := coderdtest.CreateFirstUser(t, adminClient.Client)
|
||||
memberClientRaw, _ := coderdtest.CreateAnotherUser(t, adminClient.Client, firstUser.OrganizationID)
|
||||
memberClient := codersdk.NewExperimentalClient(memberClientRaw)
|
||||
|
||||
_, err := adminClient.CreateChatProvider(ctx, codersdk.CreateChatProviderConfigRequest{
|
||||
Provider: "openai",
|
||||
APIKey: "test-api-key",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
contextLimit := int64(4096)
|
||||
enabled := false
|
||||
modelConfig, err := adminClient.CreateChatModelConfig(ctx, codersdk.CreateChatModelConfigRequest{
|
||||
Provider: "openai",
|
||||
Model: "gpt-4o-reenable",
|
||||
DisplayName: "GPT-4o Re-enable",
|
||||
Enabled: &enabled,
|
||||
ContextLimit: &contextLimit,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.False(t, modelConfig.Enabled)
|
||||
|
||||
memberConfigs, err := memberClient.ListChatModelConfigs(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
foundForMember := false
|
||||
for _, config := range memberConfigs {
|
||||
if config.ID == modelConfig.ID {
|
||||
foundForMember = true
|
||||
}
|
||||
}
|
||||
require.False(t, foundForMember)
|
||||
|
||||
enabled = true
|
||||
updated, err := adminClient.UpdateChatModelConfig(ctx, modelConfig.ID, codersdk.UpdateChatModelConfigRequest{
|
||||
Enabled: &enabled,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, modelConfig.ID, updated.ID)
|
||||
require.True(t, updated.Enabled)
|
||||
|
||||
memberConfigs, err = memberClient.ListChatModelConfigs(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
foundForMember = false
|
||||
for _, config := range memberConfigs {
|
||||
if config.ID == modelConfig.ID {
|
||||
foundForMember = true
|
||||
require.True(t, config.Enabled)
|
||||
}
|
||||
}
|
||||
require.True(t, foundForMember)
|
||||
})
|
||||
|
||||
t.Run("RejectsNegativePricing", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user