mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-24 16:05:44 +08:00
feat: add OpenAI advanced scheduler controls
Related: #1089, #408, #123
This commit is contained in:
@@ -606,6 +606,28 @@ export interface SystemSettings {
|
||||
payment_visible_method_alipay_enabled?: boolean;
|
||||
payment_visible_method_wxpay_enabled?: boolean;
|
||||
openai_advanced_scheduler_enabled?: boolean;
|
||||
openai_advanced_scheduler_sticky_weighted_enabled?: boolean;
|
||||
openai_advanced_scheduler_subscription_priority_enabled?: boolean;
|
||||
openai_advanced_scheduler_lb_top_k?: string;
|
||||
openai_advanced_scheduler_weight_priority?: string;
|
||||
openai_advanced_scheduler_weight_load?: string;
|
||||
openai_advanced_scheduler_weight_queue?: string;
|
||||
openai_advanced_scheduler_weight_error_rate?: string;
|
||||
openai_advanced_scheduler_weight_ttft?: string;
|
||||
openai_advanced_scheduler_weight_reset?: string;
|
||||
openai_advanced_scheduler_weight_quota_headroom?: string;
|
||||
openai_advanced_scheduler_weight_previous_response?: string;
|
||||
openai_advanced_scheduler_weight_session_sticky?: string;
|
||||
openai_advanced_scheduler_effective_lb_top_k?: string;
|
||||
openai_advanced_scheduler_effective_weight_priority?: string;
|
||||
openai_advanced_scheduler_effective_weight_load?: string;
|
||||
openai_advanced_scheduler_effective_weight_queue?: string;
|
||||
openai_advanced_scheduler_effective_weight_error_rate?: string;
|
||||
openai_advanced_scheduler_effective_weight_ttft?: string;
|
||||
openai_advanced_scheduler_effective_weight_reset?: string;
|
||||
openai_advanced_scheduler_effective_weight_quota_headroom?: string;
|
||||
openai_advanced_scheduler_effective_weight_previous_response?: string;
|
||||
openai_advanced_scheduler_effective_weight_session_sticky?: string;
|
||||
|
||||
// 余额、订阅到期与账号限额通知
|
||||
balance_low_notify_enabled: boolean;
|
||||
@@ -855,6 +877,18 @@ export interface UpdateSettingsRequest {
|
||||
payment_visible_method_alipay_enabled?: boolean;
|
||||
payment_visible_method_wxpay_enabled?: boolean;
|
||||
openai_advanced_scheduler_enabled?: boolean;
|
||||
openai_advanced_scheduler_sticky_weighted_enabled?: boolean;
|
||||
openai_advanced_scheduler_subscription_priority_enabled?: boolean;
|
||||
openai_advanced_scheduler_lb_top_k?: string;
|
||||
openai_advanced_scheduler_weight_priority?: string;
|
||||
openai_advanced_scheduler_weight_load?: string;
|
||||
openai_advanced_scheduler_weight_queue?: string;
|
||||
openai_advanced_scheduler_weight_error_rate?: string;
|
||||
openai_advanced_scheduler_weight_ttft?: string;
|
||||
openai_advanced_scheduler_weight_reset?: string;
|
||||
openai_advanced_scheduler_weight_quota_headroom?: string;
|
||||
openai_advanced_scheduler_weight_previous_response?: string;
|
||||
openai_advanced_scheduler_weight_session_sticky?: string;
|
||||
// 余额、订阅到期与账号限额通知
|
||||
balance_low_notify_enabled?: boolean;
|
||||
balance_low_notify_threshold?: number;
|
||||
|
||||
@@ -3265,6 +3265,7 @@ export default {
|
||||
priority: 'Priority',
|
||||
billingRateMultiplier: 'Billing Rate',
|
||||
weight: 'Weight',
|
||||
schedulerScore: 'Scheduler Score',
|
||||
status: 'Status',
|
||||
schedulable: 'Schedulable',
|
||||
todayStats: 'Today Stats',
|
||||
@@ -3276,6 +3277,12 @@ export default {
|
||||
expiresAt: 'Expires At',
|
||||
actions: 'Actions'
|
||||
},
|
||||
schedulerScore: {
|
||||
baseShort: 'Base',
|
||||
stickyShort: 'Sticky',
|
||||
ungrouped: 'Ungrouped',
|
||||
hint: 'Displayed as "group / base score / sticky bonus". The base score is computed within the current filtered candidate set and includes priority, load, queue depth, error rate, first-token latency, reset window, quota headroom, and related factors. The sticky bonus applies only when sticky weighting is enabled for previous_response_id or session_hash. Higher scores are preferred.'
|
||||
},
|
||||
usageWindowsHint: '"5h / 7d" are the upstream account\'s official rolling usage windows (e.g. OpenAI ChatGPT, Claude). They are imposed by the upstream provider on the account itself — not configured by sub2api, and unrelated to the models you map. Usage resets automatically once each window rolls over, and the limit cannot be lifted from within sub2api.',
|
||||
allPrivacyModes: 'All Privacy States',
|
||||
privacyUnset: 'Unset',
|
||||
@@ -6724,7 +6731,24 @@ export default {
|
||||
},
|
||||
openaiExperimentalScheduler: {
|
||||
title: 'OpenAI experimental scheduler policy',
|
||||
description: "Disabled by default. When enabled, this only changes the gateway's experimental account-selection policy for OpenAI traffic; it does not indicate an upstream OpenAI capability."
|
||||
description: "Disabled by default. When enabled, this only changes the gateway's experimental account-selection policy for OpenAI traffic; it does not indicate an upstream OpenAI capability.",
|
||||
stickyWeightedTitle: 'Sticky weighting',
|
||||
stickyWeightedDescription: 'When enabled, previous_response_id and session_hash affinity are scored by the advanced scheduler. When disabled, sticky accounts keep the legacy hard-hit behavior.',
|
||||
subscriptionPriorityTitle: 'Subscription priority',
|
||||
subscriptionPriorityDescription: 'When enabled, the scheduler scores ChatGPT subscription accounts first and falls back to non-subscription accounts only if no subscription slot can be acquired.',
|
||||
weightsTitle: 'Scheduler weight overrides',
|
||||
weightsDescription: 'Blank values use config/environment values; when config is not set, built-in defaults apply. Non-blank page settings take priority.',
|
||||
defaultPlaceholder: 'config/default: {value}',
|
||||
topKLabel: 'TopK',
|
||||
priorityWeight: 'Priority',
|
||||
loadWeight: 'Load',
|
||||
queueWeight: 'Queue',
|
||||
errorRateWeight: 'Error rate',
|
||||
ttftWeight: 'TTFT',
|
||||
resetWeight: 'Reset window',
|
||||
quotaHeadroomWeight: 'Quota headroom',
|
||||
previousResponseWeight: 'previous_response sticky',
|
||||
sessionStickyWeight: 'session_hash sticky'
|
||||
},
|
||||
usageRecords: {
|
||||
title: 'Usage Records',
|
||||
|
||||
@@ -3296,6 +3296,7 @@ export default {
|
||||
priority: '优先级',
|
||||
billingRateMultiplier: '账号倍率',
|
||||
weight: '权重',
|
||||
schedulerScore: '调度权值',
|
||||
status: '状态',
|
||||
schedulable: '调度',
|
||||
todayStats: '今日统计',
|
||||
@@ -3307,6 +3308,12 @@ export default {
|
||||
expiresAt: '过期时间',
|
||||
actions: '操作'
|
||||
},
|
||||
schedulerScore: {
|
||||
baseShort: '普通',
|
||||
stickyShort: '粘性',
|
||||
ungrouped: '未分组',
|
||||
hint: '显示格式为“分组名 / 基础分 / 粘性加分”。基础分按当前筛选条件限定的候选账号计算,包含优先级、负载、排队、错误率、首包延迟、重置窗口、额度余量等因子;粘性加分只在开启粘性加权时用于 previous_response_id 或 session_hash。分数越大越优先。'
|
||||
},
|
||||
usageWindowsHint: '“5h / 7d”是上游账号(如 OpenAI ChatGPT、Claude)官方的滚动用量窗口限制,由上游对账号设定,并非 sub2api 配置,也与你映射的模型无关。窗口滚动到期后用量会自动重置,无法在 sub2api 端解除该限制。',
|
||||
allPrivacyModes: '全部Privacy状态',
|
||||
privacyUnset: '未设置',
|
||||
@@ -6878,7 +6885,24 @@ export default {
|
||||
},
|
||||
openaiExperimentalScheduler: {
|
||||
title: 'OpenAI 实验调度策略',
|
||||
description: '默认关闭。开启后仅影响本网关在 OpenAI 账号间的实验性调度选择逻辑,不代表上游 OpenAI 官方能力。'
|
||||
description: '默认关闭。开启后仅影响本网关在 OpenAI 账号间的实验性调度选择逻辑,不代表上游 OpenAI 官方能力。',
|
||||
stickyWeightedTitle: '粘性加权',
|
||||
stickyWeightedDescription: '开启后 previous_response_id 和 session_hash 粘性进入高级调度打分;关闭时仍按旧逻辑硬命中粘性账号。',
|
||||
subscriptionPriorityTitle: '订阅优先',
|
||||
subscriptionPriorityDescription: '开启后先在 ChatGPT 订阅账号池中按权值选取;订阅池拿不到席位时再回退到非订阅账号池。',
|
||||
weightsTitle: '调度权值覆盖',
|
||||
weightsDescription: '留空时使用配置/环境变量值;配置未设置时使用内置默认值。页面非空设置优先。',
|
||||
defaultPlaceholder: '配置/默认:{value}',
|
||||
topKLabel: 'TopK',
|
||||
priorityWeight: '优先级',
|
||||
loadWeight: '负载',
|
||||
queueWeight: '排队',
|
||||
errorRateWeight: '错误率',
|
||||
ttftWeight: '首包延迟',
|
||||
resetWeight: '重置窗口',
|
||||
quotaHeadroomWeight: '额度余量',
|
||||
previousResponseWeight: 'previous_response 粘性',
|
||||
sessionStickyWeight: 'session_hash 粘性'
|
||||
},
|
||||
usageRecords: {
|
||||
title: '使用记录',
|
||||
|
||||
@@ -857,6 +857,13 @@ export interface Account {
|
||||
concurrency: number
|
||||
load_factor?: number | null
|
||||
current_concurrency?: number // Real-time concurrency count from Redis
|
||||
scheduler_score?: {
|
||||
base_score: number
|
||||
sticky_score?: number
|
||||
sticky_score_infinity?: boolean
|
||||
sticky_weighted_enabled: boolean
|
||||
} | null
|
||||
scheduler_scores?: AccountSchedulerGroupScore[] | null
|
||||
priority: number
|
||||
rate_multiplier?: number // Account billing multiplier (>=0, 0 means free)
|
||||
status: 'active' | 'inactive' | 'error'
|
||||
@@ -947,6 +954,16 @@ export interface Account {
|
||||
parent_chatgpt_account_id?: string
|
||||
}
|
||||
|
||||
export interface AccountSchedulerGroupScore {
|
||||
group_id?: number | null
|
||||
group_name?: string
|
||||
group_priority?: number | null
|
||||
base_score: number
|
||||
sticky_score?: number
|
||||
sticky_score_infinity?: boolean
|
||||
sticky_weighted_enabled: boolean
|
||||
}
|
||||
|
||||
// Account Usage types
|
||||
export interface WindowStats {
|
||||
requests: number
|
||||
|
||||
@@ -322,6 +322,29 @@
|
||||
<template #cell-priority="{ value }">
|
||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ value }}</span>
|
||||
</template>
|
||||
<template #header-scheduler_score="{ column }">
|
||||
<div class="flex items-center">
|
||||
<span>{{ column.label }}</span>
|
||||
<HelpTooltip :content="t('admin.accounts.schedulerScore.hint')" width-class="w-80" />
|
||||
</div>
|
||||
</template>
|
||||
<template #cell-scheduler_score="{ row }">
|
||||
<div v-if="getSchedulerScoreRows(row).length" class="flex min-w-[7rem] flex-col gap-0.5 font-mono text-[11px] leading-4">
|
||||
<div
|
||||
v-for="score in getSchedulerScoreRows(row)"
|
||||
:key="String(score.group_id)"
|
||||
class="flex items-center gap-1 whitespace-nowrap text-gray-700 dark:text-gray-300"
|
||||
:title="`${formatSchedulerScoreGroup(score)} / ${formatSchedulerScore(score.base_score)} / ${formatStickySchedulerScore(score)}`"
|
||||
>
|
||||
<span class="max-w-[4.75rem] truncate text-gray-500 dark:text-dark-400">{{ formatSchedulerScoreGroup(score) }}</span>
|
||||
<span class="text-gray-300 dark:text-gray-600">/</span>
|
||||
<span>{{ formatSchedulerScore(score.base_score) }}</span>
|
||||
<span class="text-gray-300 dark:text-gray-600">/</span>
|
||||
<span class="text-primary-700 dark:text-primary-300">{{ formatStickySchedulerScore(score) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="text-sm text-gray-400 dark:text-dark-500">-</span>
|
||||
</template>
|
||||
<template #cell-last_used_at="{ value }">
|
||||
<span class="text-sm text-gray-500 dark:text-dark-400">{{ formatRelativeTime(value) }}</span>
|
||||
</template>
|
||||
@@ -441,7 +464,7 @@ import TLSFingerprintProfilesModal from '@/components/admin/TLSFingerprintProfil
|
||||
import { buildOpenAIUsageRefreshKey } from '@/utils/accountUsageRefresh'
|
||||
import { formatDateTime, formatRelativeTime } from '@/utils/format'
|
||||
import { proxyExpiryBadgeClass, proxyExpiryLabelKey } from '@/utils/proxyExpiry'
|
||||
import type { Account, AccountPlatform, AccountType, Proxy as AccountProxy, AdminGroup, WindowStats, ClaudeModel } from '@/types'
|
||||
import type { Account, AccountPlatform, AccountSchedulerGroupScore, AccountType, Proxy as AccountProxy, AdminGroup, WindowStats, ClaudeModel } from '@/types'
|
||||
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
@@ -643,6 +666,29 @@ const autoRefreshIntervalLabel = (sec: number) => {
|
||||
return `${sec}s`
|
||||
}
|
||||
|
||||
const formatSchedulerScore = (value: unknown): string => {
|
||||
const num = Number(value)
|
||||
if (!Number.isFinite(num)) return '-'
|
||||
return num.toFixed(6).replace(/\.?0+$/, '')
|
||||
}
|
||||
|
||||
const formatStickySchedulerScore = (score: AccountSchedulerGroupScore): string => {
|
||||
if (!score) return '-'
|
||||
if (score.sticky_score_infinity) return '+∞'
|
||||
return formatSchedulerScore(score.sticky_score)
|
||||
}
|
||||
|
||||
const getSchedulerScoreRows = (account: Account): AccountSchedulerGroupScore[] => {
|
||||
if (!Array.isArray(account.scheduler_scores)) return []
|
||||
return account.scheduler_scores.filter(score => score.group_id != null)
|
||||
}
|
||||
|
||||
const formatSchedulerScoreGroup = (score: AccountSchedulerGroupScore): string => {
|
||||
if ('group_name' in score && score.group_name) return score.group_name
|
||||
if ('group_id' in score && score.group_id != null) return `#${score.group_id}`
|
||||
return '-'
|
||||
}
|
||||
|
||||
const loadSavedColumns = () => {
|
||||
try {
|
||||
const saved = localStorage.getItem(HIDDEN_COLUMNS_KEY)
|
||||
@@ -1166,6 +1212,7 @@ const allColumns = computed(() => {
|
||||
{ key: 'usage', label: t('admin.accounts.columns.usageWindows'), sortable: false },
|
||||
{ key: 'proxy', label: t('admin.accounts.columns.proxy'), sortable: false },
|
||||
{ key: 'priority', label: t('admin.accounts.columns.priority'), sortable: true },
|
||||
{ key: 'scheduler_score', label: t('admin.accounts.columns.schedulerScore'), sortable: false },
|
||||
{ key: 'rate_multiplier', label: t('admin.accounts.columns.billingRateMultiplier'), sortable: true },
|
||||
{ key: 'last_used_at', label: t('admin.accounts.columns.lastUsed'), sortable: true },
|
||||
{ key: 'created_at', label: t('admin.accounts.columns.createdAt'), sortable: true },
|
||||
|
||||
@@ -4014,6 +4014,81 @@
|
||||
</div>
|
||||
<Toggle v-model="form.openai_advanced_scheduler_enabled" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.openai_advanced_scheduler_enabled"
|
||||
class="flex items-center justify-between border-t border-gray-100 pt-5 dark:border-dark-700"
|
||||
>
|
||||
<div>
|
||||
<label
|
||||
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t("admin.settings.openaiExperimentalScheduler.stickyWeightedTitle") }}
|
||||
</label>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{
|
||||
t("admin.settings.openaiExperimentalScheduler.stickyWeightedDescription")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.openai_advanced_scheduler_sticky_weighted_enabled" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.openai_advanced_scheduler_enabled"
|
||||
class="flex items-center justify-between border-t border-gray-100 pt-5 dark:border-dark-700"
|
||||
>
|
||||
<div>
|
||||
<label
|
||||
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t("admin.settings.openaiExperimentalScheduler.subscriptionPriorityTitle") }}
|
||||
</label>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{
|
||||
t("admin.settings.openaiExperimentalScheduler.subscriptionPriorityDescription")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.openai_advanced_scheduler_subscription_priority_enabled" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.openai_advanced_scheduler_enabled"
|
||||
class="border-t border-gray-100 pt-5 dark:border-dark-700"
|
||||
>
|
||||
<div>
|
||||
<label
|
||||
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
>
|
||||
{{ t("admin.settings.openaiExperimentalScheduler.weightsTitle") }}
|
||||
</label>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{
|
||||
t("admin.settings.openaiExperimentalScheduler.weightsDescription")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-5">
|
||||
<label
|
||||
v-for="field in openAIAdvancedSchedulerWeightFields"
|
||||
:key="field.key"
|
||||
class="block"
|
||||
>
|
||||
<span class="text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ field.label }}
|
||||
</span>
|
||||
<input
|
||||
v-model="form[field.key]"
|
||||
class="input mt-1"
|
||||
inputmode="decimal"
|
||||
:placeholder="field.placeholder"
|
||||
type="text"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7903,6 +7978,18 @@ type SettingsForm = Omit<
|
||||
google_oauth_client_secret: string;
|
||||
force_email_on_third_party_signup: boolean;
|
||||
openai_advanced_scheduler_enabled: boolean;
|
||||
openai_advanced_scheduler_sticky_weighted_enabled: boolean;
|
||||
openai_advanced_scheduler_subscription_priority_enabled: boolean;
|
||||
openai_advanced_scheduler_lb_top_k: string;
|
||||
openai_advanced_scheduler_weight_priority: string;
|
||||
openai_advanced_scheduler_weight_load: string;
|
||||
openai_advanced_scheduler_weight_queue: string;
|
||||
openai_advanced_scheduler_weight_error_rate: string;
|
||||
openai_advanced_scheduler_weight_ttft: string;
|
||||
openai_advanced_scheduler_weight_reset: string;
|
||||
openai_advanced_scheduler_weight_quota_headroom: string;
|
||||
openai_advanced_scheduler_weight_previous_response: string;
|
||||
openai_advanced_scheduler_weight_session_sticky: string;
|
||||
// 系统全局平台限额 map;form 内始终归一化为全 4 平台对象(模板非空绑定依赖此不变量)
|
||||
default_platform_quotas: DefaultPlatformQuotasMap;
|
||||
};
|
||||
@@ -8094,6 +8181,18 @@ const form = reactive<SettingsForm>({
|
||||
// 分组隔离
|
||||
allow_ungrouped_key_scheduling: false,
|
||||
openai_advanced_scheduler_enabled: false,
|
||||
openai_advanced_scheduler_sticky_weighted_enabled: false,
|
||||
openai_advanced_scheduler_subscription_priority_enabled: false,
|
||||
openai_advanced_scheduler_lb_top_k: "",
|
||||
openai_advanced_scheduler_weight_priority: "",
|
||||
openai_advanced_scheduler_weight_load: "",
|
||||
openai_advanced_scheduler_weight_queue: "",
|
||||
openai_advanced_scheduler_weight_error_rate: "",
|
||||
openai_advanced_scheduler_weight_ttft: "",
|
||||
openai_advanced_scheduler_weight_reset: "",
|
||||
openai_advanced_scheduler_weight_quota_headroom: "",
|
||||
openai_advanced_scheduler_weight_previous_response: "",
|
||||
openai_advanced_scheduler_weight_session_sticky: "",
|
||||
// Gateway forwarding behavior
|
||||
enable_fingerprint_unification: true,
|
||||
enable_metadata_passthrough: false,
|
||||
@@ -8131,6 +8230,103 @@ const form = reactive<SettingsForm>({
|
||||
allow_user_view_error_requests: false,
|
||||
});
|
||||
|
||||
type OpenAIAdvancedSchedulerOverrideKey =
|
||||
| "openai_advanced_scheduler_lb_top_k"
|
||||
| "openai_advanced_scheduler_weight_priority"
|
||||
| "openai_advanced_scheduler_weight_load"
|
||||
| "openai_advanced_scheduler_weight_queue"
|
||||
| "openai_advanced_scheduler_weight_error_rate"
|
||||
| "openai_advanced_scheduler_weight_ttft"
|
||||
| "openai_advanced_scheduler_weight_reset"
|
||||
| "openai_advanced_scheduler_weight_quota_headroom"
|
||||
| "openai_advanced_scheduler_weight_previous_response"
|
||||
| "openai_advanced_scheduler_weight_session_sticky";
|
||||
|
||||
type OpenAIAdvancedSchedulerEffectiveKey =
|
||||
| "openai_advanced_scheduler_effective_lb_top_k"
|
||||
| "openai_advanced_scheduler_effective_weight_priority"
|
||||
| "openai_advanced_scheduler_effective_weight_load"
|
||||
| "openai_advanced_scheduler_effective_weight_queue"
|
||||
| "openai_advanced_scheduler_effective_weight_error_rate"
|
||||
| "openai_advanced_scheduler_effective_weight_ttft"
|
||||
| "openai_advanced_scheduler_effective_weight_reset"
|
||||
| "openai_advanced_scheduler_effective_weight_quota_headroom"
|
||||
| "openai_advanced_scheduler_effective_weight_previous_response"
|
||||
| "openai_advanced_scheduler_effective_weight_session_sticky";
|
||||
|
||||
const openAIAdvancedSchedulerWeightFields = computed<
|
||||
Array<{
|
||||
key: OpenAIAdvancedSchedulerOverrideKey;
|
||||
label: string;
|
||||
placeholder: string;
|
||||
}>
|
||||
>(() => {
|
||||
const placeholder = (
|
||||
effectiveKey: OpenAIAdvancedSchedulerEffectiveKey,
|
||||
fallbackValue: string,
|
||||
) => {
|
||||
const effectiveValue = String(
|
||||
(form as Record<string, unknown>)[effectiveKey] ?? "",
|
||||
).trim();
|
||||
return t("admin.settings.openaiExperimentalScheduler.defaultPlaceholder", {
|
||||
value: effectiveValue || fallbackValue,
|
||||
});
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
key: "openai_advanced_scheduler_lb_top_k",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.topKLabel"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_lb_top_k", "7"),
|
||||
},
|
||||
{
|
||||
key: "openai_advanced_scheduler_weight_priority",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.priorityWeight"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_weight_priority", "1"),
|
||||
},
|
||||
{
|
||||
key: "openai_advanced_scheduler_weight_load",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.loadWeight"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_weight_load", "1"),
|
||||
},
|
||||
{
|
||||
key: "openai_advanced_scheduler_weight_queue",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.queueWeight"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_weight_queue", "0.7"),
|
||||
},
|
||||
{
|
||||
key: "openai_advanced_scheduler_weight_error_rate",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.errorRateWeight"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_weight_error_rate", "0.8"),
|
||||
},
|
||||
{
|
||||
key: "openai_advanced_scheduler_weight_ttft",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.ttftWeight"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_weight_ttft", "0.5"),
|
||||
},
|
||||
{
|
||||
key: "openai_advanced_scheduler_weight_reset",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.resetWeight"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_weight_reset", "0"),
|
||||
},
|
||||
{
|
||||
key: "openai_advanced_scheduler_weight_quota_headroom",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.quotaHeadroomWeight"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_weight_quota_headroom", "0"),
|
||||
},
|
||||
{
|
||||
key: "openai_advanced_scheduler_weight_previous_response",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.previousResponseWeight"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_weight_previous_response", "5"),
|
||||
},
|
||||
{
|
||||
key: "openai_advanced_scheduler_weight_session_sticky",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.sessionStickyWeight"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_weight_session_sticky", "3"),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const authSourceDefaults = reactive<AuthSourceDefaultsState>(
|
||||
buildAuthSourceDefaultsState({}),
|
||||
);
|
||||
@@ -9359,6 +9555,30 @@ async function saveSettings() {
|
||||
form.payment_cancel_rate_limit_window_mode,
|
||||
payment_alipay_force_qrcode: form.payment_alipay_force_qrcode,
|
||||
openai_advanced_scheduler_enabled: form.openai_advanced_scheduler_enabled,
|
||||
openai_advanced_scheduler_sticky_weighted_enabled:
|
||||
form.openai_advanced_scheduler_sticky_weighted_enabled,
|
||||
openai_advanced_scheduler_subscription_priority_enabled:
|
||||
form.openai_advanced_scheduler_subscription_priority_enabled,
|
||||
openai_advanced_scheduler_lb_top_k:
|
||||
form.openai_advanced_scheduler_lb_top_k.trim(),
|
||||
openai_advanced_scheduler_weight_priority:
|
||||
form.openai_advanced_scheduler_weight_priority.trim(),
|
||||
openai_advanced_scheduler_weight_load:
|
||||
form.openai_advanced_scheduler_weight_load.trim(),
|
||||
openai_advanced_scheduler_weight_queue:
|
||||
form.openai_advanced_scheduler_weight_queue.trim(),
|
||||
openai_advanced_scheduler_weight_error_rate:
|
||||
form.openai_advanced_scheduler_weight_error_rate.trim(),
|
||||
openai_advanced_scheduler_weight_ttft:
|
||||
form.openai_advanced_scheduler_weight_ttft.trim(),
|
||||
openai_advanced_scheduler_weight_reset:
|
||||
form.openai_advanced_scheduler_weight_reset.trim(),
|
||||
openai_advanced_scheduler_weight_quota_headroom:
|
||||
form.openai_advanced_scheduler_weight_quota_headroom.trim(),
|
||||
openai_advanced_scheduler_weight_previous_response:
|
||||
form.openai_advanced_scheduler_weight_previous_response.trim(),
|
||||
openai_advanced_scheduler_weight_session_sticky:
|
||||
form.openai_advanced_scheduler_weight_session_sticky.trim(),
|
||||
// 余额、订阅到期与账号限额通知
|
||||
balance_low_notify_enabled: form.balance_low_notify_enabled,
|
||||
balance_low_notify_threshold:
|
||||
|
||||
@@ -161,6 +161,23 @@ vi.mock("vue-i18n", async () => {
|
||||
"admin.settings.payment.findProvider": "查看支持的支付方式",
|
||||
"admin.settings.openaiExperimentalScheduler.title": "OpenAI 实验调度策略",
|
||||
"admin.settings.openaiExperimentalScheduler.description": "默认关闭。开启后仅影响本网关在 OpenAI 账号间的实验性调度选择逻辑,不代表上游 OpenAI 官方能力。",
|
||||
"admin.settings.openaiExperimentalScheduler.stickyWeightedTitle": "粘性加权",
|
||||
"admin.settings.openaiExperimentalScheduler.stickyWeightedDescription": "开启后 previous_response_id 和 session_hash 粘性进入高级调度打分;关闭时仍按旧逻辑硬命中粘性账号。",
|
||||
"admin.settings.openaiExperimentalScheduler.subscriptionPriorityTitle": "订阅优先",
|
||||
"admin.settings.openaiExperimentalScheduler.subscriptionPriorityDescription": "开启后先在 ChatGPT 订阅账号池中按权值选取;订阅池拿不到席位时再回退到非订阅账号池。",
|
||||
"admin.settings.openaiExperimentalScheduler.weightsTitle": "调度权值覆盖",
|
||||
"admin.settings.openaiExperimentalScheduler.weightsDescription": "留空时使用配置/环境变量值;配置未设置时使用内置默认值。页面非空设置优先。",
|
||||
"admin.settings.openaiExperimentalScheduler.defaultPlaceholder": "配置/默认:{value}",
|
||||
"admin.settings.openaiExperimentalScheduler.topKLabel": "TopK",
|
||||
"admin.settings.openaiExperimentalScheduler.priorityWeight": "优先级",
|
||||
"admin.settings.openaiExperimentalScheduler.loadWeight": "负载",
|
||||
"admin.settings.openaiExperimentalScheduler.queueWeight": "排队",
|
||||
"admin.settings.openaiExperimentalScheduler.errorRateWeight": "错误率",
|
||||
"admin.settings.openaiExperimentalScheduler.ttftWeight": "首包延迟",
|
||||
"admin.settings.openaiExperimentalScheduler.resetWeight": "重置窗口",
|
||||
"admin.settings.openaiExperimentalScheduler.quotaHeadroomWeight": "额度余量",
|
||||
"admin.settings.openaiExperimentalScheduler.previousResponseWeight": "previous_response 粘性",
|
||||
"admin.settings.openaiExperimentalScheduler.sessionStickyWeight": "session_hash 粘性",
|
||||
"admin.settings.site.uploadImage": "上传图片",
|
||||
"admin.settings.site.remove": "移除",
|
||||
"admin.settings.platformQuota.platform": "平台",
|
||||
@@ -411,6 +428,28 @@ const baseSettingsResponse = {
|
||||
payment_visible_method_alipay_enabled: true,
|
||||
payment_visible_method_wxpay_enabled: true,
|
||||
openai_advanced_scheduler_enabled: false,
|
||||
openai_advanced_scheduler_sticky_weighted_enabled: false,
|
||||
openai_advanced_scheduler_subscription_priority_enabled: false,
|
||||
openai_advanced_scheduler_lb_top_k: "",
|
||||
openai_advanced_scheduler_weight_priority: "",
|
||||
openai_advanced_scheduler_weight_load: "",
|
||||
openai_advanced_scheduler_weight_queue: "",
|
||||
openai_advanced_scheduler_weight_error_rate: "",
|
||||
openai_advanced_scheduler_weight_ttft: "",
|
||||
openai_advanced_scheduler_weight_reset: "",
|
||||
openai_advanced_scheduler_weight_quota_headroom: "",
|
||||
openai_advanced_scheduler_weight_previous_response: "",
|
||||
openai_advanced_scheduler_weight_session_sticky: "",
|
||||
openai_advanced_scheduler_effective_lb_top_k: "7",
|
||||
openai_advanced_scheduler_effective_weight_priority: "1",
|
||||
openai_advanced_scheduler_effective_weight_load: "1",
|
||||
openai_advanced_scheduler_effective_weight_queue: "0.7",
|
||||
openai_advanced_scheduler_effective_weight_error_rate: "0.8",
|
||||
openai_advanced_scheduler_effective_weight_ttft: "0.5",
|
||||
openai_advanced_scheduler_effective_weight_reset: "0",
|
||||
openai_advanced_scheduler_effective_weight_quota_headroom: "0",
|
||||
openai_advanced_scheduler_effective_weight_previous_response: "5",
|
||||
openai_advanced_scheduler_effective_weight_session_sticky: "3",
|
||||
balance_low_notify_enabled: false,
|
||||
balance_low_notify_threshold: 0,
|
||||
balance_low_notify_recharge_url: "",
|
||||
|
||||
Reference in New Issue
Block a user