diff --git a/backend/internal/service/channel_service.go b/backend/internal/service/channel_service.go index d0698f0f..aa5e2ceb 100644 --- a/backend/internal/service/channel_service.go +++ b/backend/internal/service/channel_service.go @@ -566,15 +566,21 @@ func ReplaceModelInBody(body []byte, newModel string) []byte { // validateChannelConfig 校验渠道的定价和映射配置(冲突检测 + 区间校验 + 计费模式校验)。 // Create 和 Update 共用此函数,避免重复。 func validateChannelConfig(pricing []ChannelModelPricing, mapping map[string]map[string]string) error { + if err := validatePricingEntries(pricing); err != nil { + return err + } + return validateNoConflictingMappings(mapping) +} + +// validatePricingEntries 校验定价条目(冲突检测 + 区间校验 + 计费模式校验), +// 同时用于主渠道定价和 account_stats_pricing_rules 的内部定价。 +func validatePricingEntries(pricing []ChannelModelPricing) error { if err := validateNoConflictingModels(pricing); err != nil { return err } if err := validatePricingIntervals(pricing); err != nil { return err } - if err := validateNoConflictingMappings(mapping); err != nil { - return err - } return validatePricingBillingMode(pricing) } @@ -684,6 +690,11 @@ func (s *ChannelService) Create(ctx context.Context, input *CreateChannelInput) if err := validateChannelConfig(channel.ModelPricing, channel.ModelMapping); err != nil { return nil, err } + for i, rule := range channel.AccountStatsPricingRules { + if err := validatePricingEntries(rule.Pricing); err != nil { + return nil, fmt.Errorf("account stats pricing rule #%d: %w", i+1, err) + } + } if err := s.repo.Create(ctx, channel); err != nil { return nil, fmt.Errorf("create channel: %w", err) @@ -712,6 +723,11 @@ func (s *ChannelService) Update(ctx context.Context, id int64, input *UpdateChan if err := validateChannelConfig(channel.ModelPricing, channel.ModelMapping); err != nil { return nil, err } + for i, rule := range channel.AccountStatsPricingRules { + if err := validatePricingEntries(rule.Pricing); err != nil { + return nil, fmt.Errorf("account stats pricing rule #%d: %w", i+1, err) + } + } oldGroupIDs := s.getOldGroupIDs(ctx, id) diff --git a/frontend/src/views/admin/ChannelsView.vue b/frontend/src/views/admin/ChannelsView.vue index 52d57d74..0b37a20d 100644 --- a/frontend/src/views/admin/ChannelsView.vue +++ b/frontend/src/views/admin/ChannelsView.vue @@ -1032,15 +1032,19 @@ function formToAPI(): { group_ids: number[], model_pricing: ChannelModelPricing[ } // Collect web_search_emulation (only anthropic platform supports it) + // Always write the key so that disabling in the UI correctly sets platform to false, + // rather than leaving a stale true value from the cloned features_config. const wsEmulation: Record = {} for (const section of form.platforms) { if (!section.enabled) continue - if (section.web_search_emulation && section.platform === 'anthropic') { - wsEmulation[section.platform] = true + if (section.platform === 'anthropic') { + wsEmulation[section.platform] = !!section.web_search_emulation } } if (Object.keys(wsEmulation).length > 0) { featuresConfig.web_search_emulation = wsEmulation + } else { + delete featuresConfig.web_search_emulation } return { group_ids, model_pricing, model_mapping, features_config: featuresConfig }