From 0381414ca3af38879dc36727b234127dde295b52 Mon Sep 17 00:00:00 2001 From: erio Date: Sat, 7 Mar 2026 18:07:25 +0800 Subject: [PATCH] refactor: unify QuotaLimitCard and AffinityConfigCard under single section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move both cards under a unified "配额限制" section wrapper for Anthropic platform. Remove independent section wrappers from both components so they render as plain rounded cards. Non-Anthropic apikey accounts get their own quota section with just QuotaLimitCard. Co-Authored-By: Claude Opus 4.6 --- .../components/account/AffinityConfigCard.vue | 62 +++++++++----- .../components/account/CreateAccountModal.vue | 84 ++++++++++++------- .../components/account/EditAccountModal.vue | 46 +++++++--- .../src/components/account/QuotaLimitCard.vue | 11 +-- frontend/src/i18n/locales/en.ts | 5 +- frontend/src/i18n/locales/zh.ts | 5 +- 6 files changed, 136 insertions(+), 77 deletions(-) diff --git a/frontend/src/components/account/AffinityConfigCard.vue b/frontend/src/components/account/AffinityConfigCard.vue index c51deb9ac2..e6617fb50a 100644 --- a/frontend/src/components/account/AffinityConfigCard.vue +++ b/frontend/src/components/account/AffinityConfigCard.vue @@ -30,14 +30,29 @@ watch(localEnabled, (val) => { } }) +// Green zone: toggle + input +const baseLimitEnabled = ref(props.base != null && props.base > 0) + +watch(() => props.base, (val) => { + baseLimitEnabled.value = val != null && val > 0 +}) + +const toggleBaseLimit = () => { + baseLimitEnabled.value = !baseLimitEnabled.value + if (baseLimitEnabled.value) { + emit('update:base', 5) // default base + } else { + emit('update:base', null) + emit('update:buffer', null) + } +} + const onBaseInput = (e: Event) => { const raw = (e.target as HTMLInputElement).valueAsNumber emit('update:base', Number.isNaN(raw) ? null : Math.max(1, Math.floor(raw))) } -const hasBaseLimit = computed(() => props.base != null && props.base > 0) - -// buffer: empty = null (infinite yellow), 0 = no yellow, >0 = yellow range +// Yellow zone: "unlimited" checkbox + input const bufferIsInfinite = ref(props.buffer === null || props.buffer === undefined) watch(() => props.buffer, (val) => { @@ -80,16 +95,8 @@ const zonePreview = computed(() => { diff --git a/frontend/src/components/account/CreateAccountModal.vue b/frontend/src/components/account/CreateAccountModal.vue index be41b4cac1..48717c8fd6 100644 --- a/frontend/src/components/account/CreateAccountModal.vue +++ b/frontend/src/components/account/CreateAccountModal.vue @@ -1227,8 +1227,41 @@ - - + +
+
+

{{ t('admin.accounts.quotaLimit') }}

+

+ {{ t('admin.accounts.quotaLimitHint') }} +

+
+ + +
+ + +
+
+

{{ t('admin.accounts.quotaLimit') }}

+

+ {{ t('admin.accounts.quotaLimitHint') }} +

+
+ +
- -
-
-
- -

- {{ t('admin.accounts.quotaControl.clientAffinity.hint') }} -

-
- -
-
-
(null) +const affinityBuffer = ref(null) // Quota control state (Anthropic OAuth/SetupToken only) const windowCostEnabled = ref(false) @@ -3310,6 +3316,8 @@ const resetForm = () => { editQuotaDailyLimit.value = null editQuotaWeeklyLimit.value = null clientAffinityEnabled.value = false + affinityBase.value = null + affinityBuffer.value = null modelMappings.value = [] modelRestrictionMode.value = 'whitelist' allowedModels.value = [...claudeModels] // Default fill related models @@ -3422,8 +3430,20 @@ const buildAnthropicExtra = (base?: Record): Record) => { if (clientAffinityEnabled.value) { extra.client_affinity_enabled = true + if (affinityBase.value != null && affinityBase.value > 0) { + extra.affinity_base = affinityBase.value + } else { + delete extra.affinity_base + } + if (affinityBase.value != null && affinityBase.value > 0 && affinityBuffer.value != null) { + extra.affinity_buffer = affinityBuffer.value + } else { + delete extra.affinity_buffer + } } else { delete extra.client_affinity_enabled + delete extra.affinity_base + delete extra.affinity_buffer } } diff --git a/frontend/src/components/account/EditAccountModal.vue b/frontend/src/components/account/EditAccountModal.vue index 574d8eec0c..21031eb18b 100644 --- a/frontend/src/components/account/EditAccountModal.vue +++ b/frontend/src/components/account/EditAccountModal.vue @@ -904,19 +904,41 @@
- - - - - +
+ class="border-t border-gray-200 pt-4 dark:border-dark-600 space-y-4" + > +
+

{{ t('admin.accounts.quotaLimit') }}

+

+ {{ t('admin.accounts.quotaLimitHint') }} +

+
+ + +
+ + +
+
+

{{ t('admin.accounts.quotaLimit') }}

+

+ {{ t('admin.accounts.quotaLimitHint') }} +

+
+ +
{ diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 0f31a6122c..4d3eaf1e90 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -1795,7 +1795,7 @@ export default { resetQuota: 'Reset Quota', quotaLimit: 'Quota Limit', quotaLimitPlaceholder: '0 means unlimited', - quotaLimitHint: 'Set max spending limit (USD). Account will be paused when reached. Changing limit won\'t reset usage.', + quotaLimitHint: 'Configure account usage quota and client affinity limits.', quotaLimitToggle: 'Enable Quota Limit', quotaLimitToggleHint: 'When enabled, account will be paused when usage reaches the set limit', quotaLimitAmount: 'Total Limit', @@ -2026,7 +2026,8 @@ export default { affinityToggleHint: 'New sessions prefer accounts previously used by this client', affinityBase: 'Base Limit (Green Zone)', affinityBasePlaceholder: 'Empty = no limit', - affinityBaseHint: 'Max clients in green zone (full priority). Leave empty for unlimited.', + affinityBaseHint: 'Max clients in green zone (full priority scheduling)', + affinityBaseOffHint: 'No green zone limit. All clients receive full priority scheduling.', affinityBuffer: 'Buffer (Yellow Zone)', affinityBufferPlaceholder: 'e.g. 3', affinityBufferHint: 'Additional clients allowed in the yellow zone (degraded priority)', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index dbdccd1e71..4f47067784 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -1794,7 +1794,7 @@ export default { resetQuota: '重置配额', quotaLimit: '配额限制', quotaLimitPlaceholder: '0 表示不限制', - quotaLimitHint: '设置最大使用额度(美元),达到后账号暂停调度。修改限额不会重置已用额度。', + quotaLimitHint: '配置账号用量配额和客户端亲和限制。', quotaLimitToggle: '启用配额限制', quotaLimitToggleHint: '开启后,当账号用量达到设定额度时自动暂停调度', quotaLimitAmount: '总限额', @@ -2160,7 +2160,8 @@ export default { affinityToggleHint: '新会话优先调度到该客户端之前使用过的账号', affinityBase: '基础限额(绿区)', affinityBasePlaceholder: '留空表示不限制', - affinityBaseHint: '绿区最大客户端数量(完整优先级调度),留空表示不限制', + affinityBaseHint: '绿区最大客户端数量(完整优先级调度)', + affinityBaseOffHint: '未开启绿区限制,所有客户端均享受完整优先级调度', affinityBuffer: '缓冲区(黄区)', affinityBufferPlaceholder: '例如 3', affinityBufferHint: '黄区允许的额外客户端数量(降级优先级调度)',