Commit cfe72159 authored by erio's avatar erio
Browse files

feat(ops): add ignore insufficient balance errors toggle and extract error constants

- Add 5th error filter switch IgnoreInsufficientBalanceErrors to suppress
  upstream insufficient balance / insufficient_quota errors from ops log
- Extract hardcoded error strings into package-level constants for
  shouldSkipOpsErrorLog, normalizeOpsErrorType, classifyOpsPhase, and
  classifyOpsIsBusinessLimited
- Define ErrNoAvailableAccounts sentinel error and replace all
  errors.New("no available accounts") call sites
- Update tests to use require.ErrorIs with the sentinel error
parent 8321e4a6
...@@ -26,6 +26,21 @@ const ( ...@@ -26,6 +26,21 @@ const (
opsStreamKey = "ops_stream" opsStreamKey = "ops_stream"
opsRequestBodyKey = "ops_request_body" opsRequestBodyKey = "ops_request_body"
opsAccountIDKey = "ops_account_id" opsAccountIDKey = "ops_account_id"
// 错误过滤匹配常量 — shouldSkipOpsErrorLog 和错误分类共用
opsErrContextCanceled = "context canceled"
opsErrNoAvailableAccounts = "no available accounts"
opsErrInvalidAPIKey = "invalid_api_key"
opsErrAPIKeyRequired = "api_key_required"
opsErrInsufficientBalance = "insufficient balance"
opsErrInsufficientQuota = "insufficient_quota"
// 上游错误码常量 — 错误分类 (normalizeOpsErrorType / classifyOpsPhase / classifyOpsIsBusinessLimited)
opsCodeInsufficientBalance = "INSUFFICIENT_BALANCE"
opsCodeUsageLimitExceeded = "USAGE_LIMIT_EXCEEDED"
opsCodeSubscriptionNotFound = "SUBSCRIPTION_NOT_FOUND"
opsCodeSubscriptionInvalid = "SUBSCRIPTION_INVALID"
opsCodeUserInactive = "USER_INACTIVE"
) )
const ( const (
...@@ -1024,9 +1039,9 @@ func normalizeOpsErrorType(errType string, code string) string { ...@@ -1024,9 +1039,9 @@ func normalizeOpsErrorType(errType string, code string) string {
return errType return errType
} }
switch strings.TrimSpace(code) { switch strings.TrimSpace(code) {
case "INSUFFICIENT_BALANCE": case opsCodeInsufficientBalance:
return "billing_error" return "billing_error"
case "USAGE_LIMIT_EXCEEDED", "SUBSCRIPTION_NOT_FOUND", "SUBSCRIPTION_INVALID": case opsCodeUsageLimitExceeded, opsCodeSubscriptionNotFound, opsCodeSubscriptionInvalid:
return "subscription_error" return "subscription_error"
default: default:
return "api_error" return "api_error"
...@@ -1038,7 +1053,7 @@ func classifyOpsPhase(errType, message, code string) string { ...@@ -1038,7 +1053,7 @@ func classifyOpsPhase(errType, message, code string) string {
// Standardized phases: request|auth|routing|upstream|network|internal // Standardized phases: request|auth|routing|upstream|network|internal
// Map billing/concurrency/response => request; scheduling => routing. // Map billing/concurrency/response => request; scheduling => routing.
switch strings.TrimSpace(code) { switch strings.TrimSpace(code) {
case "INSUFFICIENT_BALANCE", "USAGE_LIMIT_EXCEEDED", "SUBSCRIPTION_NOT_FOUND", "SUBSCRIPTION_INVALID": case opsCodeInsufficientBalance, opsCodeUsageLimitExceeded, opsCodeSubscriptionNotFound, opsCodeSubscriptionInvalid:
return "request" return "request"
} }
...@@ -1057,7 +1072,7 @@ func classifyOpsPhase(errType, message, code string) string { ...@@ -1057,7 +1072,7 @@ func classifyOpsPhase(errType, message, code string) string {
case "upstream_error", "overloaded_error": case "upstream_error", "overloaded_error":
return "upstream" return "upstream"
case "api_error": case "api_error":
if strings.Contains(msg, "no available accounts") { if strings.Contains(msg, opsErrNoAvailableAccounts) {
return "routing" return "routing"
} }
return "internal" return "internal"
...@@ -1103,7 +1118,7 @@ func classifyOpsIsRetryable(errType string, statusCode int) bool { ...@@ -1103,7 +1118,7 @@ func classifyOpsIsRetryable(errType string, statusCode int) bool {
func classifyOpsIsBusinessLimited(errType, phase, code string, status int, message string) bool { func classifyOpsIsBusinessLimited(errType, phase, code string, status int, message string) bool {
switch strings.TrimSpace(code) { switch strings.TrimSpace(code) {
case "INSUFFICIENT_BALANCE", "USAGE_LIMIT_EXCEEDED", "SUBSCRIPTION_NOT_FOUND", "SUBSCRIPTION_INVALID", "USER_INACTIVE": case opsCodeInsufficientBalance, opsCodeUsageLimitExceeded, opsCodeSubscriptionNotFound, opsCodeSubscriptionInvalid, opsCodeUserInactive:
return true return true
} }
if phase == "billing" || phase == "concurrency" { if phase == "billing" || phase == "concurrency" {
...@@ -1197,21 +1212,29 @@ func shouldSkipOpsErrorLog(ctx context.Context, ops *service.OpsService, message ...@@ -1197,21 +1212,29 @@ func shouldSkipOpsErrorLog(ctx context.Context, ops *service.OpsService, message
// Check if context canceled errors should be ignored (client disconnects) // Check if context canceled errors should be ignored (client disconnects)
if settings.IgnoreContextCanceled { if settings.IgnoreContextCanceled {
if strings.Contains(msgLower, "context canceled") || strings.Contains(bodyLower, "context canceled") { if strings.Contains(msgLower, opsErrContextCanceled) || strings.Contains(bodyLower, opsErrContextCanceled) {
return true return true
} }
} }
// Check if "no available accounts" errors should be ignored // Check if "no available accounts" errors should be ignored
if settings.IgnoreNoAvailableAccounts { if settings.IgnoreNoAvailableAccounts {
if strings.Contains(msgLower, "no available accounts") || strings.Contains(bodyLower, "no available accounts") { if strings.Contains(msgLower, opsErrNoAvailableAccounts) || strings.Contains(bodyLower, opsErrNoAvailableAccounts) {
return true return true
} }
} }
// Check if invalid/missing API key errors should be ignored (user misconfiguration) // Check if invalid/missing API key errors should be ignored (user misconfiguration)
if settings.IgnoreInvalidApiKeyErrors { if settings.IgnoreInvalidApiKeyErrors {
if strings.Contains(bodyLower, "invalid_api_key") || strings.Contains(bodyLower, "api_key_required") { if strings.Contains(bodyLower, opsErrInvalidAPIKey) || strings.Contains(bodyLower, opsErrAPIKeyRequired) {
return true
}
}
// Check if insufficient balance errors should be ignored
if settings.IgnoreInsufficientBalanceErrors {
if strings.Contains(bodyLower, opsErrInsufficientBalance) || strings.Contains(bodyLower, opsErrInsufficientQuota) ||
strings.Contains(msgLower, opsErrInsufficientBalance) {
return true return true
} }
} }
......
...@@ -440,7 +440,7 @@ func TestGatewayService_SelectAccountForModelWithPlatform_NoAvailableAccounts(t ...@@ -440,7 +440,7 @@ func TestGatewayService_SelectAccountForModelWithPlatform_NoAvailableAccounts(t
acc, err := svc.selectAccountForModelWithPlatform(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, PlatformAnthropic) acc, err := svc.selectAccountForModelWithPlatform(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, PlatformAnthropic)
require.Error(t, err) require.Error(t, err)
require.Nil(t, acc) require.Nil(t, acc)
require.Contains(t, err.Error(), "no available accounts") require.ErrorIs(t, err, ErrNoAvailableAccounts)
} }
// TestGatewayService_SelectAccountForModelWithPlatform_AllExcluded 测试所有账户被排除 // TestGatewayService_SelectAccountForModelWithPlatform_AllExcluded 测试所有账户被排除
...@@ -1073,7 +1073,7 @@ func TestGatewayService_SelectAccountForModelWithPlatform_NoAccounts(t *testing. ...@@ -1073,7 +1073,7 @@ func TestGatewayService_SelectAccountForModelWithPlatform_NoAccounts(t *testing.
acc, err := svc.selectAccountForModelWithPlatform(ctx, nil, "", "", nil, PlatformAnthropic) acc, err := svc.selectAccountForModelWithPlatform(ctx, nil, "", "", nil, PlatformAnthropic)
require.Error(t, err) require.Error(t, err)
require.Nil(t, acc) require.Nil(t, acc)
require.Contains(t, err.Error(), "no available accounts") require.ErrorIs(t, err, ErrNoAvailableAccounts)
} }
func TestGatewayService_isModelSupportedByAccount(t *testing.T) { func TestGatewayService_isModelSupportedByAccount(t *testing.T) {
...@@ -1734,7 +1734,7 @@ func TestGatewayService_selectAccountWithMixedScheduling(t *testing.T) { ...@@ -1734,7 +1734,7 @@ func TestGatewayService_selectAccountWithMixedScheduling(t *testing.T) {
acc, err := svc.selectAccountWithMixedScheduling(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, PlatformAnthropic) acc, err := svc.selectAccountWithMixedScheduling(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, PlatformAnthropic)
require.Error(t, err) require.Error(t, err)
require.Nil(t, acc) require.Nil(t, acc)
require.Contains(t, err.Error(), "no available accounts") require.ErrorIs(t, err, ErrNoAvailableAccounts)
}) })
t.Run("混合调度-不支持模型返回错误", func(t *testing.T) { t.Run("混合调度-不支持模型返回错误", func(t *testing.T) {
...@@ -2290,7 +2290,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) { ...@@ -2290,7 +2290,7 @@ func TestGatewayService_SelectAccountWithLoadAwareness(t *testing.T) {
result, err := svc.SelectAccountWithLoadAwareness(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, "") result, err := svc.SelectAccountWithLoadAwareness(ctx, nil, "", "claude-3-5-sonnet-20241022", nil, "")
require.Error(t, err) require.Error(t, err)
require.Nil(t, result) require.Nil(t, result)
require.Contains(t, err.Error(), "no available accounts") require.ErrorIs(t, err, ErrNoAvailableAccounts)
}) })
t.Run("过滤不可调度账号-限流账号被跳过", func(t *testing.T) { t.Run("过滤不可调度账号-限流账号被跳过", func(t *testing.T) {
......
...@@ -346,6 +346,9 @@ var systemBlockFilterPrefixes = []string{ ...@@ -346,6 +346,9 @@ var systemBlockFilterPrefixes = []string{
"x-anthropic-billing-header", "x-anthropic-billing-header",
} }
// ErrNoAvailableAccounts 表示没有可用的账号
var ErrNoAvailableAccounts = errors.New("no available accounts")
// ErrClaudeCodeOnly 表示分组仅允许 Claude Code 客户端访问 // ErrClaudeCodeOnly 表示分组仅允许 Claude Code 客户端访问
var ErrClaudeCodeOnly = errors.New("this group only allows Claude Code clients") var ErrClaudeCodeOnly = errors.New("this group only allows Claude Code clients")
...@@ -1205,7 +1208,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -1205,7 +1208,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
return nil, err return nil, err
} }
if len(accounts) == 0 { if len(accounts) == 0 {
return nil, errors.New("no available accounts") return nil, ErrNoAvailableAccounts
} }
ctx = s.withWindowCostPrefetch(ctx, accounts) ctx = s.withWindowCostPrefetch(ctx, accounts)
ctx = s.withRPMPrefetch(ctx, accounts) ctx = s.withRPMPrefetch(ctx, accounts)
...@@ -1553,7 +1556,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -1553,7 +1556,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
} }
if len(candidates) == 0 { if len(candidates) == 0 {
return nil, errors.New("no available accounts") return nil, ErrNoAvailableAccounts
} }
accountLoads := make([]AccountWithConcurrency, 0, len(candidates)) accountLoads := make([]AccountWithConcurrency, 0, len(candidates))
...@@ -1642,7 +1645,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro ...@@ -1642,7 +1645,7 @@ func (s *GatewayService) SelectAccountWithLoadAwareness(ctx context.Context, gro
}, },
}, nil }, nil
} }
return nil, errors.New("no available accounts") return nil, ErrNoAvailableAccounts
} }
func (s *GatewayService) tryAcquireByLegacyOrder(ctx context.Context, candidates []*Account, groupID *int64, sessionHash string, preferOAuth bool) (*AccountSelectionResult, bool) { func (s *GatewayService) tryAcquireByLegacyOrder(ctx context.Context, candidates []*Account, groupID *int64, sessionHash string, preferOAuth bool) (*AccountSelectionResult, bool) {
...@@ -2852,9 +2855,9 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context, ...@@ -2852,9 +2855,9 @@ func (s *GatewayService) selectAccountForModelWithPlatform(ctx context.Context,
if selected == nil { if selected == nil {
stats := s.logDetailedSelectionFailure(ctx, groupID, sessionHash, requestedModel, platform, accounts, excludedIDs, false) stats := s.logDetailedSelectionFailure(ctx, groupID, sessionHash, requestedModel, platform, accounts, excludedIDs, false)
if requestedModel != "" { if requestedModel != "" {
return nil, fmt.Errorf("no available accounts supporting model: %s (%s)", requestedModel, summarizeSelectionFailureStats(stats)) return nil, fmt.Errorf("%w supporting model: %s (%s)", ErrNoAvailableAccounts, requestedModel, summarizeSelectionFailureStats(stats))
} }
return nil, errors.New("no available accounts") return nil, ErrNoAvailableAccounts
} }
// 4. 建立粘性绑定 // 4. 建立粘性绑定
...@@ -3090,9 +3093,9 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g ...@@ -3090,9 +3093,9 @@ func (s *GatewayService) selectAccountWithMixedScheduling(ctx context.Context, g
if selected == nil { if selected == nil {
stats := s.logDetailedSelectionFailure(ctx, groupID, sessionHash, requestedModel, nativePlatform, accounts, excludedIDs, true) stats := s.logDetailedSelectionFailure(ctx, groupID, sessionHash, requestedModel, nativePlatform, accounts, excludedIDs, true)
if requestedModel != "" { if requestedModel != "" {
return nil, fmt.Errorf("no available accounts supporting model: %s (%s)", requestedModel, summarizeSelectionFailureStats(stats)) return nil, fmt.Errorf("%w supporting model: %s (%s)", ErrNoAvailableAccounts, requestedModel, summarizeSelectionFailureStats(stats))
} }
return nil, errors.New("no available accounts") return nil, ErrNoAvailableAccounts
} }
// 4. 建立粘性绑定 // 4. 建立粘性绑定
......
...@@ -725,7 +725,7 @@ func (s *defaultOpenAIAccountScheduler) selectByLoadBalance( ...@@ -725,7 +725,7 @@ func (s *defaultOpenAIAccountScheduler) selectByLoadBalance(
}, len(candidates), topK, loadSkew, nil }, len(candidates), topK, loadSkew, nil
} }
return nil, len(candidates), topK, loadSkew, errors.New("no available accounts") return nil, len(candidates), topK, loadSkew, ErrNoAvailableAccounts
} }
func (s *defaultOpenAIAccountScheduler) isAccountTransportCompatible(account *Account, requiredTransport OpenAIUpstreamTransport) bool { func (s *defaultOpenAIAccountScheduler) isAccountTransportCompatible(account *Account, requiredTransport OpenAIUpstreamTransport) bool {
......
...@@ -1312,7 +1312,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex ...@@ -1312,7 +1312,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
return nil, err return nil, err
} }
if len(accounts) == 0 { if len(accounts) == 0 {
return nil, errors.New("no available accounts") return nil, ErrNoAvailableAccounts
} }
isExcluded := func(accountID int64) bool { isExcluded := func(accountID int64) bool {
...@@ -1382,7 +1382,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex ...@@ -1382,7 +1382,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
} }
if len(candidates) == 0 { if len(candidates) == 0 {
return nil, errors.New("no available accounts") return nil, ErrNoAvailableAccounts
} }
accountLoads := make([]AccountWithConcurrency, 0, len(candidates)) accountLoads := make([]AccountWithConcurrency, 0, len(candidates))
...@@ -1489,7 +1489,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex ...@@ -1489,7 +1489,7 @@ func (s *OpenAIGatewayService) SelectAccountWithLoadAwareness(ctx context.Contex
}, nil }, nil
} }
return nil, errors.New("no available accounts") return nil, ErrNoAvailableAccounts
} }
func (s *OpenAIGatewayService) listSchedulableAccounts(ctx context.Context, groupID *int64) ([]Account, error) { func (s *OpenAIGatewayService) listSchedulableAccounts(ctx context.Context, groupID *int64) ([]Account, error) {
......
...@@ -467,7 +467,7 @@ func (s *OpsService) executeClientRetry(ctx context.Context, reqType opsRetryReq ...@@ -467,7 +467,7 @@ func (s *OpsService) executeClientRetry(ctx context.Context, reqType opsRetryReq
return &opsRetryExecution{status: opsRetryStatusFailed, errorMessage: selErr.Error()} return &opsRetryExecution{status: opsRetryStatusFailed, errorMessage: selErr.Error()}
} }
if selection == nil || selection.Account == nil { if selection == nil || selection.Account == nil {
return &opsRetryExecution{status: opsRetryStatusFailed, errorMessage: "no available accounts"} return &opsRetryExecution{status: opsRetryStatusFailed, errorMessage: ErrNoAvailableAccounts.Error()}
} }
account := selection.Account account := selection.Account
......
...@@ -368,13 +368,14 @@ func defaultOpsAdvancedSettings() *OpsAdvancedSettings { ...@@ -368,13 +368,14 @@ func defaultOpsAdvancedSettings() *OpsAdvancedSettings {
Aggregation: OpsAggregationSettings{ Aggregation: OpsAggregationSettings{
AggregationEnabled: false, AggregationEnabled: false,
}, },
IgnoreCountTokensErrors: true, // count_tokens 404 是预期行为,默认忽略 IgnoreCountTokensErrors: true, // count_tokens 404 是预期行为,默认忽略
IgnoreContextCanceled: true, // Default to true - client disconnects are not errors IgnoreContextCanceled: true, // Default to true - client disconnects are not errors
IgnoreNoAvailableAccounts: false, // Default to false - this is a real routing issue IgnoreNoAvailableAccounts: false, // Default to false - this is a real routing issue
DisplayOpenAITokenStats: false, IgnoreInsufficientBalanceErrors: false, // 默认不忽略,余额不足可能需要关注
DisplayAlertEvents: true, DisplayOpenAITokenStats: false,
AutoRefreshEnabled: false, DisplayAlertEvents: true,
AutoRefreshIntervalSec: 30, AutoRefreshEnabled: false,
AutoRefreshIntervalSec: 30,
} }
} }
......
...@@ -92,16 +92,17 @@ type OpsAlertRuntimeSettings struct { ...@@ -92,16 +92,17 @@ type OpsAlertRuntimeSettings struct {
// OpsAdvancedSettings stores advanced ops configuration (data retention, aggregation). // OpsAdvancedSettings stores advanced ops configuration (data retention, aggregation).
type OpsAdvancedSettings struct { type OpsAdvancedSettings struct {
DataRetention OpsDataRetentionSettings `json:"data_retention"` DataRetention OpsDataRetentionSettings `json:"data_retention"`
Aggregation OpsAggregationSettings `json:"aggregation"` Aggregation OpsAggregationSettings `json:"aggregation"`
IgnoreCountTokensErrors bool `json:"ignore_count_tokens_errors"` IgnoreCountTokensErrors bool `json:"ignore_count_tokens_errors"`
IgnoreContextCanceled bool `json:"ignore_context_canceled"` IgnoreContextCanceled bool `json:"ignore_context_canceled"`
IgnoreNoAvailableAccounts bool `json:"ignore_no_available_accounts"` IgnoreNoAvailableAccounts bool `json:"ignore_no_available_accounts"`
IgnoreInvalidApiKeyErrors bool `json:"ignore_invalid_api_key_errors"` IgnoreInvalidApiKeyErrors bool `json:"ignore_invalid_api_key_errors"`
DisplayOpenAITokenStats bool `json:"display_openai_token_stats"` IgnoreInsufficientBalanceErrors bool `json:"ignore_insufficient_balance_errors"`
DisplayAlertEvents bool `json:"display_alert_events"` DisplayOpenAITokenStats bool `json:"display_openai_token_stats"`
AutoRefreshEnabled bool `json:"auto_refresh_enabled"` DisplayAlertEvents bool `json:"display_alert_events"`
AutoRefreshIntervalSec int `json:"auto_refresh_interval_seconds"` AutoRefreshEnabled bool `json:"auto_refresh_enabled"`
AutoRefreshIntervalSec int `json:"auto_refresh_interval_seconds"`
} }
type OpsDataRetentionSettings struct { type OpsDataRetentionSettings struct {
......
...@@ -841,6 +841,7 @@ export interface OpsAdvancedSettings { ...@@ -841,6 +841,7 @@ export interface OpsAdvancedSettings {
ignore_context_canceled: boolean ignore_context_canceled: boolean
ignore_no_available_accounts: boolean ignore_no_available_accounts: boolean
ignore_invalid_api_key_errors: boolean ignore_invalid_api_key_errors: boolean
ignore_insufficient_balance_errors: boolean
display_openai_token_stats: boolean display_openai_token_stats: boolean
display_alert_events: boolean display_alert_events: boolean
auto_refresh_enabled: boolean auto_refresh_enabled: boolean
......
...@@ -3842,6 +3842,8 @@ export default { ...@@ -3842,6 +3842,8 @@ export default {
ignoreNoAvailableAccountsHint: 'When enabled, "No available accounts" errors will not be written to the error log (not recommended; usually a config issue).', ignoreNoAvailableAccountsHint: 'When enabled, "No available accounts" errors will not be written to the error log (not recommended; usually a config issue).',
ignoreInvalidApiKeyErrors: 'Ignore invalid API key errors', ignoreInvalidApiKeyErrors: 'Ignore invalid API key errors',
ignoreInvalidApiKeyErrorsHint: 'When enabled, invalid or missing API key errors (INVALID_API_KEY, API_KEY_REQUIRED) will not be written to the error log.', ignoreInvalidApiKeyErrorsHint: 'When enabled, invalid or missing API key errors (INVALID_API_KEY, API_KEY_REQUIRED) will not be written to the error log.',
ignoreInsufficientBalanceErrors: 'Ignore Insufficient Balance Errors',
ignoreInsufficientBalanceErrorsHint: 'When enabled, upstream insufficient account balance errors will not be written to the error log.',
autoRefresh: 'Auto Refresh', autoRefresh: 'Auto Refresh',
enableAutoRefresh: 'Enable auto refresh', enableAutoRefresh: 'Enable auto refresh',
enableAutoRefreshHint: 'Automatically refresh dashboard data at a fixed interval.', enableAutoRefreshHint: 'Automatically refresh dashboard data at a fixed interval.',
......
...@@ -4016,6 +4016,8 @@ export default { ...@@ -4016,6 +4016,8 @@ export default {
ignoreNoAvailableAccountsHint: '启用后,"No available accounts" 错误将不会写入错误日志(不推荐,这通常是配置问题)。', ignoreNoAvailableAccountsHint: '启用后,"No available accounts" 错误将不会写入错误日志(不推荐,这通常是配置问题)。',
ignoreInvalidApiKeyErrors: '忽略无效 API Key 错误', ignoreInvalidApiKeyErrors: '忽略无效 API Key 错误',
ignoreInvalidApiKeyErrorsHint: '启用后,无效或缺失 API Key 的错误(INVALID_API_KEY、API_KEY_REQUIRED)将不会写入错误日志。', ignoreInvalidApiKeyErrorsHint: '启用后,无效或缺失 API Key 的错误(INVALID_API_KEY、API_KEY_REQUIRED)将不会写入错误日志。',
ignoreInsufficientBalanceErrors: '忽略余额不足错误',
ignoreInsufficientBalanceErrorsHint: '启用后,上游账号余额不足(Insufficient balance)的错误将不会写入错误日志。',
autoRefresh: '自动刷新', autoRefresh: '自动刷新',
enableAutoRefresh: '启用自动刷新', enableAutoRefresh: '启用自动刷新',
enableAutoRefreshHint: '自动刷新仪表板数据,启用后会定期拉取最新数据。', enableAutoRefreshHint: '自动刷新仪表板数据,启用后会定期拉取最新数据。',
......
...@@ -516,6 +516,16 @@ async function saveAllSettings() { ...@@ -516,6 +516,16 @@ async function saveAllSettings() {
</div> </div>
<Toggle v-model="advancedSettings.ignore_invalid_api_key_errors" /> <Toggle v-model="advancedSettings.ignore_invalid_api_key_errors" />
</div> </div>
<div class="flex items-center justify-between">
<div>
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.ops.settings.ignoreInsufficientBalanceErrors') }}</label>
<p class="mt-1 text-xs text-gray-500">
{{ t('admin.ops.settings.ignoreInsufficientBalanceErrorsHint') }}
</p>
</div>
<Toggle v-model="advancedSettings.ignore_insufficient_balance_errors" />
</div>
</div> </div>
<!-- Auto Refresh --> <!-- Auto Refresh -->
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment