From 45f65c297b00f12ac30b5e431ca49e8d9b07355d Mon Sep 17 00:00:00 2001 From: Seefs Date: Sun, 22 Mar 2026 15:43:03 +0800 Subject: [PATCH] feat: support regex-prefixed ignored upstream models --- controller/channel_upstream_update.go | 14 +++++++++----- controller/channel_upstream_update_test.go | 12 ++++++++++++ .../table/channels/modals/EditChannelModal.jsx | 7 ++++++- web/src/i18n/locales/en.json | 4 +++- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/controller/channel_upstream_update.go b/controller/channel_upstream_update.go index 1062adb1..1d851949 100644 --- a/controller/channel_upstream_update.go +++ b/controller/channel_upstream_update.go @@ -3,6 +3,7 @@ package controller import ( "fmt" "net/http" + "regexp" "slices" "strings" "sync" @@ -169,10 +170,7 @@ func collectPendingUpstreamModelChangesFromModels( upstreamSet[modelName] = struct{}{} } - ignoredSet := make(map[string]struct{}) - for _, modelName := range normalizeModelNames(ignoredModels) { - ignoredSet[modelName] = struct{}{} - } + normalizedIgnoredModels := normalizeModelNames(ignoredModels) redirectSourceSet := make(map[string]struct{}, len(modelMapping)) redirectTargetSet := make(map[string]struct{}, len(modelMapping)) @@ -193,7 +191,13 @@ func collectPendingUpstreamModelChangesFromModels( if _, ok := coveredUpstreamSet[modelName]; ok { return false } - if _, ok := ignoredSet[modelName]; ok { + if lo.ContainsBy(normalizedIgnoredModels, func(ignoredModel string) bool { + if regexBody, ok := strings.CutPrefix(ignoredModel, "regex:"); ok { + matched, err := regexp.MatchString(strings.TrimSpace(regexBody), modelName) + return err == nil && matched + } + return ignoredModel == modelName + }) { return false } return true diff --git a/controller/channel_upstream_update_test.go b/controller/channel_upstream_update_test.go index 153119d4..52de830b 100644 --- a/controller/channel_upstream_update_test.go +++ b/controller/channel_upstream_update_test.go @@ -111,6 +111,18 @@ func TestCollectPendingUpstreamModelChangesFromModels_WithModelMapping(t *testin require.Equal(t, []string{"stale-model"}, pendingRemoveModels) } +func TestCollectPendingUpstreamModelChangesFromModels_WithIgnoredRegexPatterns(t *testing.T) { + pendingAddModels, pendingRemoveModels := collectPendingUpstreamModelChangesFromModels( + []string{"gpt-4o"}, + []string{"gpt-4o", "claude-3-5-sonnet", "sora-video", "gpt-4.1"}, + []string{"regex:^sora-.*$", "gpt-4.1"}, + nil, + ) + + require.Equal(t, []string{"claude-3-5-sonnet"}, pendingAddModels) + require.Equal(t, []string{}, pendingRemoveModels) +} + func TestBuildUpstreamModelUpdateTaskNotificationContent_OmitOverflowDetails(t *testing.T) { channelSummaries := make([]upstreamModelUpdateChannelSummary, 0, 12) for i := 0; i < 12; i++ { diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index 7f89120b..02eef571 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -3294,7 +3294,12 @@ const EditChannelModal = (props) => { handleInputChange( 'upstream_model_update_ignored_models', diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 1482fb25..0a330432 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -3337,6 +3337,8 @@ "输入价格:{{symbol}}{{price}} / 1M tokens": "Input Price: {{symbol}}{{price}} / 1M tokens", "输出价格 {{symbol}}{{price}} / 1M tokens": "Output Price {{symbol}}{{price}} / 1M tokens", "输出价格:{{symbol}}{{price}} / 1M tokens": "Output Price: {{symbol}}{{price}} / 1M tokens", - "输出价格:{{symbol}}{{total}} / 1M tokens": "Output Price: {{symbol}}{{total}} / 1M tokens" + "输出价格:{{symbol}}{{total}} / 1M tokens": "Output Price: {{symbol}}{{total}} / 1M tokens", + "例如:gpt-4.1-nano,regex:^claude-.*$,regex:^sora-.*$": "Example: gpt-4.1-nano,regex:^claude-.*$,regex:^sora-.*$", + "支持精确匹配;使用 regex: 开头可按正则匹配。": "Supports exact matching. Use a regex: prefix for regex matching." } }