diff --git a/backend/internal/handler/admin/setting_handler.go b/backend/internal/handler/admin/setting_handler.go index a5f3d41d90..01a066165b 100644 --- a/backend/internal/handler/admin/setting_handler.go +++ b/backend/internal/handler/admin/setting_handler.go @@ -374,6 +374,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) { ChannelMonitorMode: settings.ChannelMonitorMode, ChannelMonitorDefaultIntervalSeconds: settings.ChannelMonitorDefaultIntervalSeconds, ChannelMonitorHideThroughput: settings.ChannelMonitorHideThroughput, + ChannelMonitorShowQuota: settings.ChannelMonitorShowQuota, GrokDefaultTextModel: settings.GrokDefaultTextModel, GrokCrossClientModelMapEnabled: settings.GrokCrossClientModelMapEnabled, diff --git a/backend/internal/handler/admin/setting_handler_update.go b/backend/internal/handler/admin/setting_handler_update.go index 5884dc392e..e2399d0891 100644 --- a/backend/internal/handler/admin/setting_handler_update.go +++ b/backend/internal/handler/admin/setting_handler_update.go @@ -332,6 +332,7 @@ type UpdateSettingsRequest struct { ChannelMonitorMode *string `json:"channel_monitor_mode"` ChannelMonitorDefaultIntervalSeconds *int `json:"channel_monitor_default_interval_seconds"` ChannelMonitorHideThroughput *bool `json:"channel_monitor_hide_throughput"` + ChannelMonitorShowQuota *bool `json:"channel_monitor_show_quota"` // Grok model mapping policy GrokDefaultTextModel *string `json:"grok_default_text_model"` @@ -1889,6 +1890,12 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { } return previousSettings.ChannelMonitorHideThroughput }(), + ChannelMonitorShowQuota: func() bool { + if req.ChannelMonitorShowQuota != nil { + return *req.ChannelMonitorShowQuota + } + return previousSettings.ChannelMonitorShowQuota + }(), GrokDefaultTextModel: func() string { if req.GrokDefaultTextModel != nil { return *req.GrokDefaultTextModel @@ -2342,6 +2349,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) { ChannelMonitorMode: updatedSettings.ChannelMonitorMode, ChannelMonitorDefaultIntervalSeconds: updatedSettings.ChannelMonitorDefaultIntervalSeconds, ChannelMonitorHideThroughput: updatedSettings.ChannelMonitorHideThroughput, + ChannelMonitorShowQuota: updatedSettings.ChannelMonitorShowQuota, GrokDefaultTextModel: updatedSettings.GrokDefaultTextModel, GrokCrossClientModelMapEnabled: updatedSettings.GrokCrossClientModelMapEnabled, diff --git a/backend/internal/handler/dto/settings.go b/backend/internal/handler/dto/settings.go index 5ab5eaa68b..f0284eb55e 100644 --- a/backend/internal/handler/dto/settings.go +++ b/backend/internal/handler/dto/settings.go @@ -305,6 +305,7 @@ type SystemSettings struct { ChannelMonitorMode string `json:"channel_monitor_mode"` ChannelMonitorDefaultIntervalSeconds int `json:"channel_monitor_default_interval_seconds"` ChannelMonitorHideThroughput bool `json:"channel_monitor_hide_throughput"` + ChannelMonitorShowQuota bool `json:"channel_monitor_show_quota"` // Grok model mapping policy (admin settings; empty account mapping falls back to these). GrokDefaultTextModel string `json:"grok_default_text_model"` @@ -414,6 +415,7 @@ type PublicSettings struct { ChannelMonitorMode string `json:"channel_monitor_mode"` ChannelMonitorDefaultIntervalSeconds int `json:"channel_monitor_default_interval_seconds"` ChannelMonitorHideThroughput bool `json:"channel_monitor_hide_throughput"` + ChannelMonitorShowQuota bool `json:"channel_monitor_show_quota"` AvailableChannelsEnabled bool `json:"available_channels_enabled"` diff --git a/backend/internal/handler/setting_handler.go b/backend/internal/handler/setting_handler.go index a415d67e34..f0785de996 100644 --- a/backend/internal/handler/setting_handler.go +++ b/backend/internal/handler/setting_handler.go @@ -107,6 +107,7 @@ func (h *SettingHandler) GetPublicSettings(c *gin.Context) { ChannelMonitorMode: settings.ChannelMonitorMode, ChannelMonitorDefaultIntervalSeconds: settings.ChannelMonitorDefaultIntervalSeconds, ChannelMonitorHideThroughput: settings.ChannelMonitorHideThroughput, + ChannelMonitorShowQuota: settings.ChannelMonitorShowQuota, AvailableChannelsEnabled: settings.AvailableChannelsEnabled, diff --git a/backend/internal/server/api_contract_test.go b/backend/internal/server/api_contract_test.go index b0b1df7bc0..7f3b25f763 100644 --- a/backend/internal/server/api_contract_test.go +++ b/backend/internal/server/api_contract_test.go @@ -988,6 +988,7 @@ func TestAPIContracts(t *testing.T) { "channel_monitor_enabled": true, "channel_monitor_mode": "v1", "channel_monitor_hide_throughput": true, + "channel_monitor_show_quota": false, "channel_monitor_default_interval_seconds": 60, "available_channels_enabled": false, "model_plaza_enabled": false, @@ -1298,6 +1299,7 @@ func TestAPIContracts(t *testing.T) { "channel_monitor_enabled": true, "channel_monitor_mode": "v1", "channel_monitor_hide_throughput": true, + "channel_monitor_show_quota": false, "channel_monitor_default_interval_seconds": 60, "available_channels_enabled": false, "model_plaza_enabled": false, diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go index c0efa2b527..ba35301be0 100644 --- a/backend/internal/service/domain_constants.go +++ b/backend/internal/service/domain_constants.go @@ -477,6 +477,13 @@ const ( // Default false (show rates). Admin endpoints always keep full metrics. SettingKeyChannelMonitorHideThroughput = "channel_monitor_hide_throughput" + // SettingKeyChannelMonitorShowQuota controls whether quota/balance snapshots + // attached to channel monitors (check_mode=quota/quota_probe) are exposed on + // the user-facing monitor APIs and UI. Default false (hidden); parsed + // fail-closed (only the literal "true" enables it). Admin endpoints always + // keep the full snapshots regardless of this flag. + SettingKeyChannelMonitorShowQuota = "channel_monitor_show_quota" + // SettingKeyGrokDefaultTextModel is the fallback Grok text model for empty // request models and built-in Grok aliases (e.g. "grok" → this id). Default grok-4.5. SettingKeyGrokDefaultTextModel = "grok_default_text_model" diff --git a/backend/internal/service/setting_parse.go b/backend/internal/service/setting_parse.go index 283c76f8f4..9f3dfbddf2 100644 --- a/backend/internal/service/setting_parse.go +++ b/backend/internal/service/setting_parse.go @@ -190,6 +190,7 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error { SettingKeyChannelMonitorMode: ChannelMonitorModeV1, SettingKeyChannelMonitorDefaultIntervalSeconds: "60", SettingKeyChannelMonitorHideThroughput: "true", + SettingKeyChannelMonitorShowQuota: "false", // Grok: safe defaults — no cross-vendor model rewrite unless operators enable it. SettingKeyGrokDefaultTextModel: "grok-4.5", @@ -798,6 +799,9 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin // 默认隐藏吞吐(迁移 206 的隐私默认):未配置时必须与 setting_public.go 的 // 公开读取路径给出同一个值,否则管理端看到“未隐藏”而用户端实际已隐藏。 result.ChannelMonitorHideThroughput = !isFalseSettingValue(settings[SettingKeyChannelMonitorHideThroughput]) + // 配额展示默认关闭且 fail-closed:仅字面 "true" 视为开启 + // (与 setting_public.go 公开读取路径保持一致)。 + result.ChannelMonitorShowQuota = settings[SettingKeyChannelMonitorShowQuota] == "true" // Grok default mapping policy result.GrokDefaultTextModel = strings.TrimSpace(settings[SettingKeyGrokDefaultTextModel]) diff --git a/backend/internal/service/setting_public.go b/backend/internal/service/setting_public.go index f292e8ac57..4679d00484 100644 --- a/backend/internal/service/setting_public.go +++ b/backend/internal/service/setting_public.go @@ -231,6 +231,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings SettingKeyChannelMonitorMode, SettingKeyChannelMonitorDefaultIntervalSeconds, SettingKeyChannelMonitorHideThroughput, + SettingKeyChannelMonitorShowQuota, SettingKeyAvailableChannelsEnabled, SettingKeyModelPlazaEnabled, SettingKeyModelPlazaRequireAuth, @@ -355,6 +356,7 @@ func (s *SettingService) GetPublicSettings(ctx context.Context) (*PublicSettings ChannelMonitorMode: normalizeChannelMonitorMode(settings[SettingKeyChannelMonitorMode]), ChannelMonitorDefaultIntervalSeconds: parseChannelMonitorInterval(settings[SettingKeyChannelMonitorDefaultIntervalSeconds]), ChannelMonitorHideThroughput: !isFalseSettingValue(settings[SettingKeyChannelMonitorHideThroughput]), + ChannelMonitorShowQuota: settings[SettingKeyChannelMonitorShowQuota] == "true", AvailableChannelsEnabled: settings[SettingKeyAvailableChannelsEnabled] == "true", @@ -422,6 +424,10 @@ type ChannelMonitorRuntime struct { DefaultIntervalSeconds int // HideThroughput: when true, user-facing V2 APIs omit RPM/TPM scale signals. HideThroughput bool + // ShowQuota: when true, user-facing monitor views keep the quota/balance + // snapshots; otherwise the user handler strips them server-side. + // Parsed fail-closed (only literal "true" enables). Admin always sees them. + ShowQuota bool } // ActiveProbesAllowed reports whether V1 active provider probes may run. @@ -450,6 +456,7 @@ func (s *SettingService) GetChannelMonitorRuntime(ctx context.Context) ChannelMo SettingKeyChannelMonitorMode, SettingKeyChannelMonitorDefaultIntervalSeconds, SettingKeyChannelMonitorHideThroughput, + SettingKeyChannelMonitorShowQuota, }) if err != nil { return ChannelMonitorRuntime{ @@ -464,6 +471,7 @@ func (s *SettingService) GetChannelMonitorRuntime(ctx context.Context) ChannelMo Mode: normalizeChannelMonitorMode(vals[SettingKeyChannelMonitorMode]), DefaultIntervalSeconds: parseChannelMonitorInterval(vals[SettingKeyChannelMonitorDefaultIntervalSeconds]), HideThroughput: !isFalseSettingValue(vals[SettingKeyChannelMonitorHideThroughput]), + ShowQuota: vals[SettingKeyChannelMonitorShowQuota] == "true", } } @@ -606,6 +614,9 @@ type PublicSettingsInjectionPayload struct { // ChannelMonitorHideThroughput is public so the user UI can hide RPM/TPM // without waiting for API redaction alone (defense in depth). ChannelMonitorHideThroughput bool `json:"channel_monitor_hide_throughput"` + // ChannelMonitorShowQuota gates the user-facing quota/balance display on + // monitors; fail-closed (absent/false = hidden). Admin UI always shows it. + ChannelMonitorShowQuota bool `json:"channel_monitor_show_quota"` AvailableChannelsEnabled bool `json:"available_channels_enabled"` ModelPlazaEnabled bool `json:"model_plaza_enabled"` ModelPlazaRequireAuth bool `json:"model_plaza_require_auth"` @@ -685,6 +696,7 @@ func (s *SettingService) GetPublicSettingsForInjection(ctx context.Context) (any ChannelMonitorMode: settings.ChannelMonitorMode, ChannelMonitorDefaultIntervalSeconds: settings.ChannelMonitorDefaultIntervalSeconds, ChannelMonitorHideThroughput: settings.ChannelMonitorHideThroughput, + ChannelMonitorShowQuota: settings.ChannelMonitorShowQuota, AvailableChannelsEnabled: settings.AvailableChannelsEnabled, ModelPlazaEnabled: settings.ModelPlazaEnabled, ModelPlazaRequireAuth: settings.ModelPlazaRequireAuth, diff --git a/backend/internal/service/setting_update.go b/backend/internal/service/setting_update.go index f77a914fd7..a4fadded0c 100644 --- a/backend/internal/service/setting_update.go +++ b/backend/internal/service/setting_update.go @@ -417,6 +417,7 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting updates[SettingKeyChannelMonitorDefaultIntervalSeconds] = strconv.Itoa(v) } updates[SettingKeyChannelMonitorHideThroughput] = strconv.FormatBool(settings.ChannelMonitorHideThroughput) + updates[SettingKeyChannelMonitorShowQuota] = strconv.FormatBool(settings.ChannelMonitorShowQuota) // Grok model mapping policy if v := strings.TrimSpace(settings.GrokDefaultTextModel); v != "" { diff --git a/backend/internal/service/settings_view.go b/backend/internal/service/settings_view.go index 8580f0c8fb..a939000947 100644 --- a/backend/internal/service/settings_view.go +++ b/backend/internal/service/settings_view.go @@ -201,6 +201,7 @@ type SystemSettings struct { ChannelMonitorMode string `json:"channel_monitor_mode"` ChannelMonitorDefaultIntervalSeconds int `json:"channel_monitor_default_interval_seconds"` ChannelMonitorHideThroughput bool `json:"channel_monitor_hide_throughput"` + ChannelMonitorShowQuota bool `json:"channel_monitor_show_quota"` // Grok model mapping policy (admin settings; empty mapping falls back to these). GrokDefaultTextModel string `json:"grok_default_text_model"` @@ -378,6 +379,7 @@ type PublicSettings struct { ChannelMonitorMode string `json:"channel_monitor_mode"` ChannelMonitorDefaultIntervalSeconds int `json:"channel_monitor_default_interval_seconds"` ChannelMonitorHideThroughput bool `json:"channel_monitor_hide_throughput"` + ChannelMonitorShowQuota bool `json:"channel_monitor_show_quota"` // Grok model mapping policy (admin settings). GrokDefaultTextModel string `json:"grok_default_text_model"`