Merge pull request #858 from james-6-23/fix/pool-mode-03bf3485

支持 API Key 上游池模式的同账号重试次数配置与自定义错误策略
This commit is contained in:
Wesley Liddick
2026-03-09 08:48:53 +08:00
committed by GitHub
13 changed files with 558 additions and 40 deletions

View File

@@ -1127,6 +1127,58 @@
</template>
</div>
<!-- Pool Mode Section -->
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
<div class="mb-3 flex items-center justify-between">
<div>
<label class="input-label mb-0">{{ t('admin.accounts.poolMode') }}</label>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.accounts.poolModeHint') }}
</p>
</div>
<button
type="button"
@click="poolModeEnabled = !poolModeEnabled"
:class="[
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
poolModeEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
]"
>
<span
:class="[
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
poolModeEnabled ? 'translate-x-5' : 'translate-x-0'
]"
/>
</button>
</div>
<div v-if="poolModeEnabled" class="rounded-lg bg-blue-50 p-3 dark:bg-blue-900/20">
<p class="text-xs text-blue-700 dark:text-blue-400">
<Icon name="exclamationCircle" size="sm" class="mr-1 inline" :stroke-width="2" />
{{ t('admin.accounts.poolModeInfo') }}
</p>
</div>
<div v-if="poolModeEnabled" class="mt-3">
<label class="input-label">{{ t('admin.accounts.poolModeRetryCount') }}</label>
<input
v-model.number="poolModeRetryCount"
type="number"
min="0"
:max="MAX_POOL_MODE_RETRY_COUNT"
step="1"
class="input"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{
t('admin.accounts.poolModeRetryCountHint', {
default: DEFAULT_POOL_MODE_RETRY_COUNT,
max: MAX_POOL_MODE_RETRY_COUNT
})
}}
</p>
</div>
</div>
<!-- Custom Error Codes Section -->
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
<div class="mb-3 flex items-center justify-between">
@@ -2629,6 +2681,10 @@ const editQuotaWeeklyLimit = ref<number | null>(null)
const modelMappings = ref<ModelMapping[]>([])
const modelRestrictionMode = ref<'whitelist' | 'mapping'>('whitelist')
const allowedModels = ref<string[]>([])
const DEFAULT_POOL_MODE_RETRY_COUNT = 3
const MAX_POOL_MODE_RETRY_COUNT = 10
const poolModeEnabled = ref(false)
const poolModeRetryCount = ref(DEFAULT_POOL_MODE_RETRY_COUNT)
const customErrorCodesEnabled = ref(false)
const selectedErrorCodes = ref<number[]>([])
const customErrorCodeInput = ref<number | null>(null)
@@ -3300,6 +3356,8 @@ const resetForm = () => {
fetchAntigravityDefaultMappings().then(mappings => {
antigravityModelMappings.value = [...mappings]
})
poolModeEnabled.value = false
poolModeRetryCount.value = DEFAULT_POOL_MODE_RETRY_COUNT
customErrorCodesEnabled.value = false
selectedErrorCodes.value = []
customErrorCodeInput.value = null
@@ -3452,6 +3510,20 @@ const handleMixedChannelCancel = () => {
clearMixedChannelDialog()
}
const normalizePoolModeRetryCount = (value: number) => {
if (!Number.isFinite(value)) {
return DEFAULT_POOL_MODE_RETRY_COUNT
}
const normalized = Math.trunc(value)
if (normalized < 0) {
return 0
}
if (normalized > MAX_POOL_MODE_RETRY_COUNT) {
return MAX_POOL_MODE_RETRY_COUNT
}
return normalized
}
const handleSubmit = async () => {
// For OAuth-based type, handle OAuth flow (goes to step 2)
if (isOAuthFlow.value) {
@@ -3551,6 +3623,12 @@ const handleSubmit = async () => {
}
}
// Add pool mode if enabled
if (poolModeEnabled.value) {
credentials.pool_mode = true
credentials.pool_mode_retry_count = normalizePoolModeRetryCount(poolModeRetryCount.value)
}
// Add custom error codes if enabled
if (customErrorCodesEnabled.value) {
credentials.custom_error_codes_enabled = true

View File

@@ -251,6 +251,58 @@
</template>
</div>
<!-- Pool Mode Section -->
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
<div class="mb-3 flex items-center justify-between">
<div>
<label class="input-label mb-0">{{ t('admin.accounts.poolMode') }}</label>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.accounts.poolModeHint') }}
</p>
</div>
<button
type="button"
@click="poolModeEnabled = !poolModeEnabled"
:class="[
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
poolModeEnabled ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
]"
>
<span
:class="[
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
poolModeEnabled ? 'translate-x-5' : 'translate-x-0'
]"
/>
</button>
</div>
<div v-if="poolModeEnabled" class="rounded-lg bg-blue-50 p-3 dark:bg-blue-900/20">
<p class="text-xs text-blue-700 dark:text-blue-400">
<Icon name="exclamationCircle" size="sm" class="mr-1 inline" :stroke-width="2" />
{{ t('admin.accounts.poolModeInfo') }}
</p>
</div>
<div v-if="poolModeEnabled" class="mt-3">
<label class="input-label">{{ t('admin.accounts.poolModeRetryCount') }}</label>
<input
v-model.number="poolModeRetryCount"
type="number"
min="0"
:max="MAX_POOL_MODE_RETRY_COUNT"
step="1"
class="input"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
{{
t('admin.accounts.poolModeRetryCountHint', {
default: DEFAULT_POOL_MODE_RETRY_COUNT,
max: MAX_POOL_MODE_RETRY_COUNT
})
}}
</p>
</div>
</div>
<!-- Custom Error Codes Section -->
<div class="border-t border-gray-200 pt-4 dark:border-dark-600">
<div class="mb-3 flex items-center justify-between">
@@ -1498,6 +1550,10 @@ const editApiKey = ref('')
const modelMappings = ref<ModelMapping[]>([])
const modelRestrictionMode = ref<'whitelist' | 'mapping'>('whitelist')
const allowedModels = ref<string[]>([])
const DEFAULT_POOL_MODE_RETRY_COUNT = 3
const MAX_POOL_MODE_RETRY_COUNT = 10
const poolModeEnabled = ref(false)
const poolModeRetryCount = ref(DEFAULT_POOL_MODE_RETRY_COUNT)
const customErrorCodesEnabled = ref(false)
const selectedErrorCodes = ref<number[]>([])
const customErrorCodeInput = ref<number | null>(null)
@@ -1658,6 +1714,20 @@ const expiresAtInput = computed({
})
// Watchers
const normalizePoolModeRetryCount = (value: number) => {
if (!Number.isFinite(value)) {
return DEFAULT_POOL_MODE_RETRY_COUNT
}
const normalized = Math.trunc(value)
if (normalized < 0) {
return 0
}
if (normalized > MAX_POOL_MODE_RETRY_COUNT) {
return MAX_POOL_MODE_RETRY_COUNT
}
return normalized
}
watch(
() => props.account,
(newAccount) => {
@@ -1805,6 +1875,12 @@ watch(
allowedModels.value = []
}
// Load pool mode
poolModeEnabled.value = credentials.pool_mode === true
poolModeRetryCount.value = normalizePoolModeRetryCount(
Number(credentials.pool_mode_retry_count ?? DEFAULT_POOL_MODE_RETRY_COUNT)
)
// Load custom error codes
customErrorCodesEnabled.value = credentials.custom_error_codes_enabled === true
const existingErrorCodes = credentials.custom_error_codes as number[] | undefined
@@ -1851,6 +1927,8 @@ watch(
modelMappings.value = []
allowedModels.value = []
}
poolModeEnabled.value = false
poolModeRetryCount.value = DEFAULT_POOL_MODE_RETRY_COUNT
customErrorCodesEnabled.value = false
selectedErrorCodes.value = []
}
@@ -2311,6 +2389,15 @@ const handleSubmit = async () => {
newCredentials.model_mapping = currentCredentials.model_mapping
}
// Add pool mode if enabled
if (poolModeEnabled.value) {
newCredentials.pool_mode = true
newCredentials.pool_mode_retry_count = normalizePoolModeRetryCount(poolModeRetryCount.value)
} else {
delete newCredentials.pool_mode
delete newCredentials.pool_mode_retry_count
}
// Add custom error codes if enabled
if (customErrorCodesEnabled.value) {
newCredentials.custom_error_codes_enabled = true

View File

@@ -1942,6 +1942,13 @@ export default {
addModel: 'Add',
modelExists: 'Model already exists',
modelCount: '{count} models',
poolMode: 'Pool Mode',
poolModeHint: 'Enable when upstream is an account pool; errors won\'t mark local account status',
poolModeInfo:
'When enabled, upstream 429/403/401 errors will auto-retry without marking the account as rate-limited or errored. Suitable for upstream pointing to another sub2api instance.',
poolModeRetryCount: 'Same-Account Retries',
poolModeRetryCountHint:
'Only applies in pool mode. Use 0 to disable in-place retry. Default {default}, maximum {max}.',
customErrorCodes: 'Custom Error Codes',
customErrorCodesHint: 'Only stop scheduling for selected error codes',
customErrorCodesWarning:

View File

@@ -2086,6 +2086,12 @@ export default {
addModel: '填入',
modelExists: '该模型已存在',
modelCount: '{count} 个模型',
poolMode: '池模式',
poolModeHint: '上游为账号池时启用,错误不标记本地账号状态',
poolModeInfo:
'启用后,上游 429/403/401 错误将自动重试而不标记账号限流或错误,适用于上游指向另一个 sub2api 实例的场景。',
poolModeRetryCount: '同账号重试次数',
poolModeRetryCountHint: '仅在池模式下生效。0 表示不原地重试;默认 {default},最大 {max}。',
customErrorCodes: '自定义错误码',
customErrorCodesHint: '仅对选中的错误码停止调度',
customErrorCodesWarning: '仅选中的错误码会停止调度,其他错误将返回 500。',