Merge pull request #798 from touwaeriol/feature/account-load-factor
feat: add account load_factor for scheduling load calculation
This commit is contained in:
@@ -469,7 +469,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Concurrency & Priority -->
|
||||
<div class="grid grid-cols-2 gap-4 border-t border-gray-200 pt-4 dark:border-dark-600 lg:grid-cols-3">
|
||||
<div class="grid grid-cols-2 gap-4 border-t border-gray-200 pt-4 dark:border-dark-600 lg:grid-cols-4">
|
||||
<div>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<label
|
||||
@@ -496,8 +496,39 @@
|
||||
class="input"
|
||||
:class="!enableConcurrency && 'cursor-not-allowed opacity-50'"
|
||||
aria-labelledby="bulk-edit-concurrency-label"
|
||||
@input="concurrency = Math.max(1, concurrency || 1)"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<label
|
||||
id="bulk-edit-load-factor-label"
|
||||
class="input-label mb-0"
|
||||
for="bulk-edit-load-factor-enabled"
|
||||
>
|
||||
{{ t('admin.accounts.loadFactor') }}
|
||||
</label>
|
||||
<input
|
||||
v-model="enableLoadFactor"
|
||||
id="bulk-edit-load-factor-enabled"
|
||||
type="checkbox"
|
||||
aria-controls="bulk-edit-load-factor"
|
||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
v-model.number="loadFactor"
|
||||
id="bulk-edit-load-factor"
|
||||
type="number"
|
||||
min="1"
|
||||
:disabled="!enableLoadFactor"
|
||||
class="input"
|
||||
:class="!enableLoadFactor && 'cursor-not-allowed opacity-50'"
|
||||
aria-labelledby="bulk-edit-load-factor-label"
|
||||
@input="loadFactor = (loadFactor && loadFactor >= 1) ? loadFactor : null"
|
||||
/>
|
||||
<p class="input-hint">{{ t('admin.accounts.loadFactorHint') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<label
|
||||
@@ -869,6 +900,7 @@ const enableCustomErrorCodes = ref(false)
|
||||
const enableInterceptWarmup = ref(false)
|
||||
const enableProxy = ref(false)
|
||||
const enableConcurrency = ref(false)
|
||||
const enableLoadFactor = ref(false)
|
||||
const enablePriority = ref(false)
|
||||
const enableRateMultiplier = ref(false)
|
||||
const enableStatus = ref(false)
|
||||
@@ -889,6 +921,7 @@ const customErrorCodeInput = ref<number | null>(null)
|
||||
const interceptWarmupRequests = ref(false)
|
||||
const proxyId = ref<number | null>(null)
|
||||
const concurrency = ref(1)
|
||||
const loadFactor = ref<number | null>(null)
|
||||
const priority = ref(1)
|
||||
const rateMultiplier = ref(1)
|
||||
const status = ref<'active' | 'inactive'>('active')
|
||||
@@ -1195,6 +1228,12 @@ const buildUpdatePayload = (): Record<string, unknown> | null => {
|
||||
updates.concurrency = concurrency.value
|
||||
}
|
||||
|
||||
if (enableLoadFactor.value) {
|
||||
// 空值/NaN/0 时发送 0(后端约定 <= 0 表示清除)
|
||||
const lf = loadFactor.value
|
||||
updates.load_factor = (lf != null && !Number.isNaN(lf) && lf > 0) ? lf : 0
|
||||
}
|
||||
|
||||
if (enablePriority.value) {
|
||||
updates.priority = priority.value
|
||||
}
|
||||
@@ -1340,6 +1379,7 @@ const handleSubmit = async () => {
|
||||
enableInterceptWarmup.value ||
|
||||
enableProxy.value ||
|
||||
enableConcurrency.value ||
|
||||
enableLoadFactor.value ||
|
||||
enablePriority.value ||
|
||||
enableRateMultiplier.value ||
|
||||
enableStatus.value ||
|
||||
@@ -1430,6 +1470,7 @@ watch(
|
||||
enableInterceptWarmup.value = false
|
||||
enableProxy.value = false
|
||||
enableConcurrency.value = false
|
||||
enableLoadFactor.value = false
|
||||
enablePriority.value = false
|
||||
enableRateMultiplier.value = false
|
||||
enableStatus.value = false
|
||||
@@ -1446,6 +1487,7 @@ watch(
|
||||
interceptWarmupRequests.value = false
|
||||
proxyId.value = null
|
||||
concurrency.value = 1
|
||||
loadFactor.value = null
|
||||
priority.value = 1
|
||||
rateMultiplier.value = 1
|
||||
status.value = 'active'
|
||||
|
||||
@@ -1752,10 +1752,18 @@
|
||||
<ProxySelector v-model="form.proxy_id" :proxies="proxies" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 lg:grid-cols-3">
|
||||
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.concurrency') }}</label>
|
||||
<input v-model.number="form.concurrency" type="number" min="1" class="input" />
|
||||
<input v-model.number="form.concurrency" type="number" min="1" class="input"
|
||||
@input="form.concurrency = Math.max(1, form.concurrency || 1)" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.loadFactor') }}</label>
|
||||
<input v-model.number="form.load_factor" type="number" min="1"
|
||||
class="input" :placeholder="String(form.concurrency || 1)"
|
||||
@input="form.load_factor = (form.load_factor && form.load_factor >= 1) ? form.load_factor : null" />
|
||||
<p class="input-hint">{{ t('admin.accounts.loadFactorHint') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.priority') }}</label>
|
||||
@@ -2638,6 +2646,7 @@ const form = reactive({
|
||||
credentials: {} as Record<string, unknown>,
|
||||
proxy_id: null as number | null,
|
||||
concurrency: 10,
|
||||
load_factor: null as number | null,
|
||||
priority: 1,
|
||||
rate_multiplier: 1,
|
||||
group_ids: [] as number[],
|
||||
@@ -3117,6 +3126,7 @@ const resetForm = () => {
|
||||
form.credentials = {}
|
||||
form.proxy_id = null
|
||||
form.concurrency = 10
|
||||
form.load_factor = null
|
||||
form.priority = 1
|
||||
form.rate_multiplier = 1
|
||||
form.group_ids = []
|
||||
@@ -3489,6 +3499,7 @@ const handleImportAccessToken = async (accessTokenInput: string) => {
|
||||
extra: soraExtra,
|
||||
proxy_id: form.proxy_id,
|
||||
concurrency: form.concurrency,
|
||||
load_factor: form.load_factor ?? undefined,
|
||||
priority: form.priority,
|
||||
rate_multiplier: form.rate_multiplier,
|
||||
group_ids: form.group_ids,
|
||||
@@ -3553,6 +3564,7 @@ const createAccountAndFinish = async (
|
||||
extra: finalExtra,
|
||||
proxy_id: form.proxy_id,
|
||||
concurrency: form.concurrency,
|
||||
load_factor: form.load_factor ?? undefined,
|
||||
priority: form.priority,
|
||||
rate_multiplier: form.rate_multiplier,
|
||||
group_ids: form.group_ids,
|
||||
@@ -3608,6 +3620,7 @@ const handleOpenAIExchange = async (authCode: string) => {
|
||||
extra,
|
||||
proxy_id: form.proxy_id,
|
||||
concurrency: form.concurrency,
|
||||
load_factor: form.load_factor ?? undefined,
|
||||
priority: form.priority,
|
||||
rate_multiplier: form.rate_multiplier,
|
||||
group_ids: form.group_ids,
|
||||
@@ -3637,6 +3650,7 @@ const handleOpenAIExchange = async (authCode: string) => {
|
||||
extra: soraExtra,
|
||||
proxy_id: form.proxy_id,
|
||||
concurrency: form.concurrency,
|
||||
load_factor: form.load_factor ?? undefined,
|
||||
priority: form.priority,
|
||||
rate_multiplier: form.rate_multiplier,
|
||||
group_ids: form.group_ids,
|
||||
@@ -3714,6 +3728,7 @@ const handleOpenAIValidateRT = async (refreshTokenInput: string) => {
|
||||
extra,
|
||||
proxy_id: form.proxy_id,
|
||||
concurrency: form.concurrency,
|
||||
load_factor: form.load_factor ?? undefined,
|
||||
priority: form.priority,
|
||||
rate_multiplier: form.rate_multiplier,
|
||||
group_ids: form.group_ids,
|
||||
@@ -3741,6 +3756,7 @@ const handleOpenAIValidateRT = async (refreshTokenInput: string) => {
|
||||
extra: soraExtra,
|
||||
proxy_id: form.proxy_id,
|
||||
concurrency: form.concurrency,
|
||||
load_factor: form.load_factor ?? undefined,
|
||||
priority: form.priority,
|
||||
rate_multiplier: form.rate_multiplier,
|
||||
group_ids: form.group_ids,
|
||||
@@ -3829,6 +3845,7 @@ const handleSoraValidateST = async (sessionTokenInput: string) => {
|
||||
extra: soraExtra,
|
||||
proxy_id: form.proxy_id,
|
||||
concurrency: form.concurrency,
|
||||
load_factor: form.load_factor ?? undefined,
|
||||
priority: form.priority,
|
||||
rate_multiplier: form.rate_multiplier,
|
||||
group_ids: form.group_ids,
|
||||
@@ -3917,6 +3934,7 @@ const handleAntigravityValidateRT = async (refreshTokenInput: string) => {
|
||||
extra: {},
|
||||
proxy_id: form.proxy_id,
|
||||
concurrency: form.concurrency,
|
||||
load_factor: form.load_factor ?? undefined,
|
||||
priority: form.priority,
|
||||
rate_multiplier: form.rate_multiplier,
|
||||
group_ids: form.group_ids,
|
||||
@@ -4075,8 +4093,11 @@ const handleAnthropicExchange = async (authCode: string) => {
|
||||
}
|
||||
|
||||
// Add RPM limit settings
|
||||
if (rpmLimitEnabled.value && baseRpm.value != null && baseRpm.value > 0) {
|
||||
extra.base_rpm = baseRpm.value
|
||||
if (rpmLimitEnabled.value) {
|
||||
const DEFAULT_BASE_RPM = 15
|
||||
extra.base_rpm = (baseRpm.value != null && baseRpm.value > 0)
|
||||
? baseRpm.value
|
||||
: DEFAULT_BASE_RPM
|
||||
extra.rpm_strategy = rpmStrategy.value
|
||||
if (rpmStickyBuffer.value != null && rpmStickyBuffer.value > 0) {
|
||||
extra.rpm_sticky_buffer = rpmStickyBuffer.value
|
||||
@@ -4187,8 +4208,11 @@ const handleCookieAuth = async (sessionKey: string) => {
|
||||
}
|
||||
|
||||
// Add RPM limit settings
|
||||
if (rpmLimitEnabled.value && baseRpm.value != null && baseRpm.value > 0) {
|
||||
extra.base_rpm = baseRpm.value
|
||||
if (rpmLimitEnabled.value) {
|
||||
const DEFAULT_BASE_RPM = 15
|
||||
extra.base_rpm = (baseRpm.value != null && baseRpm.value > 0)
|
||||
? baseRpm.value
|
||||
: DEFAULT_BASE_RPM
|
||||
extra.rpm_strategy = rpmStrategy.value
|
||||
if (rpmStickyBuffer.value != null && rpmStickyBuffer.value > 0) {
|
||||
extra.rpm_sticky_buffer = rpmStickyBuffer.value
|
||||
@@ -4234,6 +4258,7 @@ const handleCookieAuth = async (sessionKey: string) => {
|
||||
extra,
|
||||
proxy_id: form.proxy_id,
|
||||
concurrency: form.concurrency,
|
||||
load_factor: form.load_factor ?? undefined,
|
||||
priority: form.priority,
|
||||
rate_multiplier: form.rate_multiplier,
|
||||
group_ids: form.group_ids,
|
||||
|
||||
@@ -650,10 +650,18 @@
|
||||
<ProxySelector v-model="form.proxy_id" :proxies="proxies" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 lg:grid-cols-3">
|
||||
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.concurrency') }}</label>
|
||||
<input v-model.number="form.concurrency" type="number" min="1" class="input" />
|
||||
<input v-model.number="form.concurrency" type="number" min="1" class="input"
|
||||
@input="form.concurrency = Math.max(1, form.concurrency || 1)" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.loadFactor') }}</label>
|
||||
<input v-model.number="form.load_factor" type="number" min="1"
|
||||
class="input" :placeholder="String(form.concurrency || 1)"
|
||||
@input="form.load_factor = (form.load_factor && form.load_factor >= 1) ? form.load_factor : null" />
|
||||
<p class="input-hint">{{ t('admin.accounts.loadFactorHint') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.accounts.priority') }}</label>
|
||||
@@ -1470,6 +1478,7 @@ const form = reactive({
|
||||
notes: '',
|
||||
proxy_id: null as number | null,
|
||||
concurrency: 1,
|
||||
load_factor: null as number | null,
|
||||
priority: 1,
|
||||
rate_multiplier: 1,
|
||||
status: 'active' as 'active' | 'inactive',
|
||||
@@ -1503,9 +1512,12 @@ watch(
|
||||
form.notes = newAccount.notes || ''
|
||||
form.proxy_id = newAccount.proxy_id
|
||||
form.concurrency = newAccount.concurrency
|
||||
form.load_factor = newAccount.load_factor ?? null
|
||||
form.priority = newAccount.priority
|
||||
form.rate_multiplier = newAccount.rate_multiplier ?? 1
|
||||
form.status = newAccount.status as 'active' | 'inactive'
|
||||
form.status = (newAccount.status === 'active' || newAccount.status === 'inactive')
|
||||
? newAccount.status
|
||||
: 'active'
|
||||
form.group_ids = newAccount.group_ids || []
|
||||
form.expires_at = newAccount.expires_at ?? null
|
||||
|
||||
@@ -2053,6 +2065,11 @@ const handleSubmit = async () => {
|
||||
if (!props.account) return
|
||||
const accountID = props.account.id
|
||||
|
||||
if (form.status !== 'active' && form.status !== 'inactive') {
|
||||
appStore.showError(t('admin.accounts.pleaseSelectStatus'))
|
||||
return
|
||||
}
|
||||
|
||||
const updatePayload: Record<string, unknown> = { ...form }
|
||||
try {
|
||||
// 后端期望 proxy_id: 0 表示清除代理,而不是 null
|
||||
@@ -2062,6 +2079,11 @@ const handleSubmit = async () => {
|
||||
if (form.expires_at === null) {
|
||||
updatePayload.expires_at = 0
|
||||
}
|
||||
// load_factor: 空值/NaN/0/负数 时发送 0(后端约定 <= 0 = 清除)
|
||||
const lf = form.load_factor
|
||||
if (lf == null || Number.isNaN(lf) || lf <= 0) {
|
||||
updatePayload.load_factor = 0
|
||||
}
|
||||
updatePayload.auto_pause_on_expired = autoPauseOnExpired.value
|
||||
|
||||
// For apikey type, handle credentials update
|
||||
@@ -2201,8 +2223,11 @@ const handleSubmit = async () => {
|
||||
}
|
||||
|
||||
// RPM limit settings
|
||||
if (rpmLimitEnabled.value && baseRpm.value != null && baseRpm.value > 0) {
|
||||
newExtra.base_rpm = baseRpm.value
|
||||
if (rpmLimitEnabled.value) {
|
||||
const DEFAULT_BASE_RPM = 15
|
||||
newExtra.base_rpm = (baseRpm.value != null && baseRpm.value > 0)
|
||||
? baseRpm.value
|
||||
: DEFAULT_BASE_RPM
|
||||
newExtra.rpm_strategy = rpmStrategy.value
|
||||
if (rpmStickyBuffer.value != null && rpmStickyBuffer.value > 0) {
|
||||
newExtra.rpm_sticky_buffer = rpmStickyBuffer.value
|
||||
|
||||
@@ -2003,10 +2003,12 @@ export default {
|
||||
proxy: 'Proxy',
|
||||
noProxy: 'No Proxy',
|
||||
concurrency: 'Concurrency',
|
||||
loadFactor: 'Load Factor',
|
||||
loadFactorHint: 'Higher load factor increases scheduling frequency',
|
||||
priority: 'Priority',
|
||||
priorityHint: 'Lower value accounts are used first',
|
||||
billingRateMultiplier: 'Billing Rate Multiplier',
|
||||
billingRateMultiplierHint: '>=0, 0 means free. Affects account billing only',
|
||||
billingRateMultiplierHint: '0 = free, affects account billing only',
|
||||
expiresAt: 'Expires At',
|
||||
expiresAtHint: 'Leave empty for no expiration',
|
||||
higherPriorityFirst: 'Lower value means higher priority',
|
||||
@@ -2022,6 +2024,7 @@ export default {
|
||||
accountUpdated: 'Account updated successfully',
|
||||
failedToCreate: 'Failed to create account',
|
||||
failedToUpdate: 'Failed to update account',
|
||||
pleaseSelectStatus: 'Please select a valid account status',
|
||||
mixedChannelWarningTitle: 'Mixed Channel Warning',
|
||||
mixedChannelWarning: 'Warning: Group "{groupName}" contains both {currentPlatform} and {otherPlatform} accounts. Mixing different channels may cause thinking block signature validation issues, which will fallback to non-thinking mode. Are you sure you want to continue?',
|
||||
pleaseEnterAccountName: 'Please enter account name',
|
||||
|
||||
@@ -2145,10 +2145,12 @@ export default {
|
||||
proxy: '代理',
|
||||
noProxy: '无代理',
|
||||
concurrency: '并发数',
|
||||
loadFactor: '负载因子',
|
||||
loadFactorHint: '提高负载因子可以提高对账号的调度频率',
|
||||
priority: '优先级',
|
||||
priorityHint: '优先级越小的账号优先使用',
|
||||
billingRateMultiplier: '账号计费倍率',
|
||||
billingRateMultiplierHint: '>=0,0 表示该账号计费为 0;仅影响账号计费口径',
|
||||
billingRateMultiplierHint: '0 表示不计费,仅影响账号计费',
|
||||
expiresAt: '过期时间',
|
||||
expiresAtHint: '留空表示不过期',
|
||||
higherPriorityFirst: '数值越小优先级越高',
|
||||
@@ -2164,6 +2166,7 @@ export default {
|
||||
accountUpdated: '账号更新成功',
|
||||
failedToCreate: '创建账号失败',
|
||||
failedToUpdate: '更新账号失败',
|
||||
pleaseSelectStatus: '请选择有效的账号状态',
|
||||
mixedChannelWarningTitle: '混合渠道警告',
|
||||
mixedChannelWarning: '警告:分组 "{groupName}" 中同时包含 {currentPlatform} 和 {otherPlatform} 账号。混合使用不同渠道可能导致 thinking block 签名验证问题,会自动回退到非 thinking 模式。确定要继续吗?',
|
||||
pleaseEnterAccountName: '请输入账号名称',
|
||||
|
||||
@@ -653,6 +653,7 @@ export interface Account {
|
||||
} & Record<string, unknown>)
|
||||
proxy_id: number | null
|
||||
concurrency: number
|
||||
load_factor?: number | null
|
||||
current_concurrency?: number // Real-time concurrency count from Redis
|
||||
priority: number
|
||||
rate_multiplier?: number // Account billing multiplier (>=0, 0 means free)
|
||||
@@ -787,6 +788,7 @@ export interface CreateAccountRequest {
|
||||
extra?: Record<string, unknown>
|
||||
proxy_id?: number | null
|
||||
concurrency?: number
|
||||
load_factor?: number | null
|
||||
priority?: number
|
||||
rate_multiplier?: number // Account billing multiplier (>=0, 0 means free)
|
||||
group_ids?: number[]
|
||||
@@ -803,6 +805,7 @@ export interface UpdateAccountRequest {
|
||||
extra?: Record<string, unknown>
|
||||
proxy_id?: number | null
|
||||
concurrency?: number
|
||||
load_factor?: number | null
|
||||
priority?: number
|
||||
rate_multiplier?: number // Account billing multiplier (>=0, 0 means free)
|
||||
schedulable?: boolean
|
||||
|
||||
Reference in New Issue
Block a user