mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 17:27:58 +08:00
feat: 按上游计费倍率调度 OpenAI 账号
This commit is contained in:
@@ -607,6 +607,8 @@ export interface SystemSettings {
|
||||
payment_visible_method_wxpay_source?: string;
|
||||
payment_visible_method_alipay_enabled?: boolean;
|
||||
payment_visible_method_wxpay_enabled?: boolean;
|
||||
openai_low_upstream_rate_priority_enabled?: boolean;
|
||||
openai_oauth_scheduling_rate_multiplier?: number;
|
||||
openai_advanced_scheduler_enabled?: boolean;
|
||||
openai_advanced_scheduler_sticky_weighted_enabled?: boolean;
|
||||
openai_advanced_scheduler_subscription_priority_enabled?: boolean;
|
||||
@@ -618,6 +620,7 @@ export interface SystemSettings {
|
||||
openai_advanced_scheduler_weight_ttft?: string;
|
||||
openai_advanced_scheduler_weight_reset?: string;
|
||||
openai_advanced_scheduler_weight_quota_headroom?: string;
|
||||
openai_advanced_scheduler_weight_upstream_cost?: string;
|
||||
openai_advanced_scheduler_weight_previous_response?: string;
|
||||
openai_advanced_scheduler_weight_session_sticky?: string;
|
||||
openai_advanced_scheduler_effective_lb_top_k?: string;
|
||||
@@ -628,6 +631,7 @@ export interface SystemSettings {
|
||||
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_upstream_cost?: string;
|
||||
openai_advanced_scheduler_effective_weight_previous_response?: string;
|
||||
openai_advanced_scheduler_effective_weight_session_sticky?: string;
|
||||
|
||||
@@ -880,6 +884,8 @@ export interface UpdateSettingsRequest {
|
||||
payment_visible_method_wxpay_source?: string;
|
||||
payment_visible_method_alipay_enabled?: boolean;
|
||||
payment_visible_method_wxpay_enabled?: boolean;
|
||||
openai_low_upstream_rate_priority_enabled?: boolean;
|
||||
openai_oauth_scheduling_rate_multiplier?: number;
|
||||
openai_advanced_scheduler_enabled?: boolean;
|
||||
openai_advanced_scheduler_sticky_weighted_enabled?: boolean;
|
||||
openai_advanced_scheduler_subscription_priority_enabled?: boolean;
|
||||
@@ -891,6 +897,7 @@ export interface UpdateSettingsRequest {
|
||||
openai_advanced_scheduler_weight_ttft?: string;
|
||||
openai_advanced_scheduler_weight_reset?: string;
|
||||
openai_advanced_scheduler_weight_quota_headroom?: string;
|
||||
openai_advanced_scheduler_weight_upstream_cost?: string;
|
||||
openai_advanced_scheduler_weight_previous_response?: string;
|
||||
openai_advanced_scheduler_weight_session_sticky?: string;
|
||||
// 余额、订阅到期与账号限额通知
|
||||
|
||||
@@ -170,7 +170,7 @@ export default {
|
||||
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.'
|
||||
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, billing rate, 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.',
|
||||
upstreamBilling: {
|
||||
|
||||
@@ -1105,6 +1105,11 @@ 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.",
|
||||
lowRatePriorityTitle: 'Prefer lower rates',
|
||||
lowRatePriorityDescription: 'When enabled, accounts with lower billing rates are preferred. If rates are equal, account priority, current load, and other scheduling factors are considered. This switch is ignored when the experimental scheduler is enabled.',
|
||||
oauthRateTitle: 'OAuth scheduling reference rate',
|
||||
oauthRatePriorityDescription: 'When a group contains both API Key and OAuth accounts, this rate is used to order OAuth accounts alongside probed API Key billing rates.',
|
||||
oauthRateWeightedDescription: 'When a group contains both API Key and OAuth accounts, this rate is used for OAuth accounts when calculating the billing-rate score.',
|
||||
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',
|
||||
@@ -1120,6 +1125,7 @@ export default {
|
||||
ttftWeight: 'TTFT',
|
||||
resetWeight: 'Reset window',
|
||||
quotaHeadroomWeight: 'Quota headroom',
|
||||
upstreamCostWeight: 'Billing rate',
|
||||
previousResponseWeight: 'previous_response sticky',
|
||||
sessionStickyWeight: 'session_hash sticky'
|
||||
},
|
||||
|
||||
@@ -126,7 +126,7 @@ export default {
|
||||
baseShort: '普通',
|
||||
stickyShort: '粘性',
|
||||
ungrouped: '未分组',
|
||||
hint: '显示格式为“分组名 / 基础分 / 粘性加分”。基础分按当前筛选条件限定的候选账号计算,包含优先级、负载、排队、错误率、首包延迟、重置窗口、额度余量等因子;粘性加分只在开启粘性加权时用于 previous_response_id 或 session_hash。分数越大越优先。'
|
||||
hint: '显示格式为“分组名 / 基础分 / 粘性加分”。基础分按当前筛选条件限定的候选账号计算,包含优先级、负载、排队、错误率、首包延迟、重置窗口、额度余量、计费倍率等因子;粘性加分只在开启粘性加权时用于 previous_response_id 或 session_hash。分数越大越优先。'
|
||||
},
|
||||
usageWindowsHint: '“5h / 7d”是上游账号(如 OpenAI ChatGPT、Claude)官方的滚动用量窗口限制,由上游对账号设定,并非 sub2api 配置,也与你映射的模型无关。窗口滚动到期后用量会自动重置,无法在 sub2api 端解除该限制。',
|
||||
upstreamBilling: {
|
||||
|
||||
@@ -1100,6 +1100,11 @@ export default {
|
||||
openaiExperimentalScheduler: {
|
||||
title: 'OpenAI 实验调度策略',
|
||||
description: '默认关闭。开启后仅影响本网关在 OpenAI 账号间的实验性调度选择逻辑,不代表上游 OpenAI 官方能力。',
|
||||
lowRatePriorityTitle: '低倍率优先',
|
||||
lowRatePriorityDescription: '开启后优先选择计费倍率较低的账号;倍率相同时,再比较账号优先级和当前负载等。启用实验调度策略后,此开关不生效。',
|
||||
oauthRateTitle: 'OAuth 调度参考倍率',
|
||||
oauthRatePriorityDescription: '同一分组同时包含 API Key 和 OAuth 账号时,OAuth 账号按此倍率与已探测的 API Key 计费倍率一起排序。',
|
||||
oauthRateWeightedDescription: '同一分组同时包含 API Key 和 OAuth 账号时,计算“计费倍率”得分时,OAuth 账号按此倍率参与计算。',
|
||||
stickyWeightedTitle: '粘性加权',
|
||||
stickyWeightedDescription: '开启后 previous_response_id 和 session_hash 粘性进入高级调度打分;关闭时仍按旧逻辑硬命中粘性账号。',
|
||||
subscriptionPriorityTitle: '订阅优先',
|
||||
@@ -1115,6 +1120,7 @@ export default {
|
||||
ttftWeight: '首包延迟',
|
||||
resetWeight: '重置窗口',
|
||||
quotaHeadroomWeight: '额度余量',
|
||||
upstreamCostWeight: '计费倍率',
|
||||
previousResponseWeight: 'previous_response 粘性',
|
||||
sessionStickyWeight: 'session_hash 粘性'
|
||||
},
|
||||
|
||||
@@ -4020,7 +4020,61 @@
|
||||
<Toggle v-model="form.allow_ungrouped_key_scheduling" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<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.lowRatePriorityTitle") }}
|
||||
</label>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{
|
||||
t("admin.settings.openaiExperimentalScheduler.lowRatePriorityDescription")
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle
|
||||
v-model="form.openai_low_upstream_rate_priority_enabled"
|
||||
data-testid="openai-low-rate-priority-toggle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!form.openai_advanced_scheduler_enabled && form.openai_low_upstream_rate_priority_enabled"
|
||||
class="flex flex-col items-stretch gap-3 border-t border-gray-100 pt-5 sm:flex-row sm:items-start sm:justify-between sm:gap-6 dark:border-dark-700"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<label
|
||||
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
for="openai-oauth-scheduling-rate-multiplier"
|
||||
>
|
||||
{{ t("admin.settings.openaiExperimentalScheduler.oauthRateTitle") }}
|
||||
</label>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.openaiExperimentalScheduler.oauthRatePriorityDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="relative w-full shrink-0 sm:w-32">
|
||||
<input
|
||||
id="openai-oauth-scheduling-rate-multiplier"
|
||||
v-model.number="form.openai_oauth_scheduling_rate_multiplier"
|
||||
class="input pr-8"
|
||||
data-testid="openai-oauth-scheduling-rate-multiplier"
|
||||
min="0"
|
||||
required
|
||||
step="0.01"
|
||||
type="number"
|
||||
/>
|
||||
<span
|
||||
class="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-sm text-gray-400"
|
||||
>x</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div 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"
|
||||
@@ -4033,7 +4087,10 @@
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="form.openai_advanced_scheduler_enabled" />
|
||||
<Toggle
|
||||
v-model="form.openai_advanced_scheduler_enabled"
|
||||
data-testid="openai-advanced-scheduler-toggle"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -4074,6 +4131,38 @@
|
||||
<Toggle v-model="form.openai_advanced_scheduler_subscription_priority_enabled" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.openai_advanced_scheduler_enabled"
|
||||
class="flex flex-col items-stretch gap-3 border-t border-gray-100 pt-5 sm:flex-row sm:items-start sm:justify-between sm:gap-6 dark:border-dark-700"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<label
|
||||
class="text-sm font-medium text-gray-700 dark:text-gray-300"
|
||||
for="openai-oauth-scheduling-rate-multiplier"
|
||||
>
|
||||
{{ t("admin.settings.openaiExperimentalScheduler.oauthRateTitle") }}
|
||||
</label>
|
||||
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t("admin.settings.openaiExperimentalScheduler.oauthRateWeightedDescription") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="relative w-full shrink-0 sm:w-32">
|
||||
<input
|
||||
id="openai-oauth-scheduling-rate-multiplier"
|
||||
v-model.number="form.openai_oauth_scheduling_rate_multiplier"
|
||||
class="input pr-8"
|
||||
data-testid="openai-oauth-scheduling-rate-multiplier"
|
||||
min="0"
|
||||
required
|
||||
step="0.01"
|
||||
type="number"
|
||||
/>
|
||||
<span
|
||||
class="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-sm text-gray-400"
|
||||
>x</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.openai_advanced_scheduler_enabled"
|
||||
class="border-t border-gray-100 pt-5 dark:border-dark-700"
|
||||
@@ -8039,6 +8128,8 @@ type SettingsForm = Omit<
|
||||
github_oauth_client_secret: string;
|
||||
google_oauth_client_secret: string;
|
||||
force_email_on_third_party_signup: boolean;
|
||||
openai_low_upstream_rate_priority_enabled: boolean;
|
||||
openai_oauth_scheduling_rate_multiplier: number;
|
||||
openai_advanced_scheduler_enabled: boolean;
|
||||
openai_advanced_scheduler_sticky_weighted_enabled: boolean;
|
||||
openai_advanced_scheduler_subscription_priority_enabled: boolean;
|
||||
@@ -8050,6 +8141,7 @@ type SettingsForm = Omit<
|
||||
openai_advanced_scheduler_weight_ttft: string;
|
||||
openai_advanced_scheduler_weight_reset: string;
|
||||
openai_advanced_scheduler_weight_quota_headroom: string;
|
||||
openai_advanced_scheduler_weight_upstream_cost: string;
|
||||
openai_advanced_scheduler_weight_previous_response: string;
|
||||
openai_advanced_scheduler_weight_session_sticky: string;
|
||||
// 系统全局平台限额 map;form 内始终归一化为全 4 平台对象(模板非空绑定依赖此不变量)
|
||||
@@ -8244,6 +8336,8 @@ const form = reactive<SettingsForm>({
|
||||
max_claude_code_version: "",
|
||||
// 分组隔离
|
||||
allow_ungrouped_key_scheduling: false,
|
||||
openai_low_upstream_rate_priority_enabled: false,
|
||||
openai_oauth_scheduling_rate_multiplier: 1,
|
||||
openai_advanced_scheduler_enabled: false,
|
||||
openai_advanced_scheduler_sticky_weighted_enabled: false,
|
||||
openai_advanced_scheduler_subscription_priority_enabled: false,
|
||||
@@ -8255,6 +8349,7 @@ const form = reactive<SettingsForm>({
|
||||
openai_advanced_scheduler_weight_ttft: "",
|
||||
openai_advanced_scheduler_weight_reset: "",
|
||||
openai_advanced_scheduler_weight_quota_headroom: "",
|
||||
openai_advanced_scheduler_weight_upstream_cost: "",
|
||||
openai_advanced_scheduler_weight_previous_response: "",
|
||||
openai_advanced_scheduler_weight_session_sticky: "",
|
||||
// Gateway forwarding behavior
|
||||
@@ -8303,6 +8398,7 @@ type OpenAIAdvancedSchedulerOverrideKey =
|
||||
| "openai_advanced_scheduler_weight_ttft"
|
||||
| "openai_advanced_scheduler_weight_reset"
|
||||
| "openai_advanced_scheduler_weight_quota_headroom"
|
||||
| "openai_advanced_scheduler_weight_upstream_cost"
|
||||
| "openai_advanced_scheduler_weight_previous_response"
|
||||
| "openai_advanced_scheduler_weight_session_sticky";
|
||||
|
||||
@@ -8315,6 +8411,7 @@ type OpenAIAdvancedSchedulerEffectiveKey =
|
||||
| "openai_advanced_scheduler_effective_weight_ttft"
|
||||
| "openai_advanced_scheduler_effective_weight_reset"
|
||||
| "openai_advanced_scheduler_effective_weight_quota_headroom"
|
||||
| "openai_advanced_scheduler_effective_weight_upstream_cost"
|
||||
| "openai_advanced_scheduler_effective_weight_previous_response"
|
||||
| "openai_advanced_scheduler_effective_weight_session_sticky";
|
||||
|
||||
@@ -8378,6 +8475,11 @@ const openAIAdvancedSchedulerWeightFields = computed<
|
||||
label: t("admin.settings.openaiExperimentalScheduler.quotaHeadroomWeight"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_weight_quota_headroom", "0"),
|
||||
},
|
||||
{
|
||||
key: "openai_advanced_scheduler_weight_upstream_cost",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.upstreamCostWeight"),
|
||||
placeholder: placeholder("openai_advanced_scheduler_effective_weight_upstream_cost", "0"),
|
||||
},
|
||||
{
|
||||
key: "openai_advanced_scheduler_weight_previous_response",
|
||||
label: t("admin.settings.openaiExperimentalScheduler.previousResponseWeight"),
|
||||
@@ -9622,6 +9724,10 @@ async function saveSettings() {
|
||||
payment_cancel_rate_limit_window_mode:
|
||||
form.payment_cancel_rate_limit_window_mode,
|
||||
payment_alipay_force_qrcode: form.payment_alipay_force_qrcode,
|
||||
openai_low_upstream_rate_priority_enabled:
|
||||
form.openai_low_upstream_rate_priority_enabled,
|
||||
openai_oauth_scheduling_rate_multiplier:
|
||||
form.openai_oauth_scheduling_rate_multiplier,
|
||||
openai_advanced_scheduler_enabled: form.openai_advanced_scheduler_enabled,
|
||||
openai_advanced_scheduler_sticky_weighted_enabled:
|
||||
form.openai_advanced_scheduler_sticky_weighted_enabled,
|
||||
@@ -9643,6 +9749,8 @@ async function saveSettings() {
|
||||
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_upstream_cost:
|
||||
form.openai_advanced_scheduler_weight_upstream_cost.trim(),
|
||||
openai_advanced_scheduler_weight_previous_response:
|
||||
form.openai_advanced_scheduler_weight_previous_response.trim(),
|
||||
openai_advanced_scheduler_weight_session_sticky:
|
||||
|
||||
@@ -161,6 +161,11 @@ vi.mock("vue-i18n", async () => {
|
||||
"admin.settings.payment.findProvider": "查看支持的支付方式",
|
||||
"admin.settings.openaiExperimentalScheduler.title": "OpenAI 实验调度策略",
|
||||
"admin.settings.openaiExperimentalScheduler.description": "默认关闭。开启后仅影响本网关在 OpenAI 账号间的实验性调度选择逻辑,不代表上游 OpenAI 官方能力。",
|
||||
"admin.settings.openaiExperimentalScheduler.lowRatePriorityTitle": "低倍率优先",
|
||||
"admin.settings.openaiExperimentalScheduler.lowRatePriorityDescription": "开启后优先选择计费倍率较低的账号;倍率相同时,再比较账号优先级和当前负载等。启用实验调度策略后,此开关不生效。",
|
||||
"admin.settings.openaiExperimentalScheduler.oauthRateTitle": "OAuth 调度参考倍率",
|
||||
"admin.settings.openaiExperimentalScheduler.oauthRatePriorityDescription": "同一分组同时包含 API Key 和 OAuth 账号时,OAuth 账号按此倍率与已探测的 API Key 计费倍率一起排序。",
|
||||
"admin.settings.openaiExperimentalScheduler.oauthRateWeightedDescription": "同一分组同时包含 API Key 和 OAuth 账号时,计算“计费倍率”得分时,OAuth 账号按此倍率参与计算。",
|
||||
"admin.settings.openaiExperimentalScheduler.stickyWeightedTitle": "粘性加权",
|
||||
"admin.settings.openaiExperimentalScheduler.stickyWeightedDescription": "开启后 previous_response_id 和 session_hash 粘性进入高级调度打分;关闭时仍按旧逻辑硬命中粘性账号。",
|
||||
"admin.settings.openaiExperimentalScheduler.subscriptionPriorityTitle": "订阅优先",
|
||||
@@ -176,6 +181,7 @@ vi.mock("vue-i18n", async () => {
|
||||
"admin.settings.openaiExperimentalScheduler.ttftWeight": "首包延迟",
|
||||
"admin.settings.openaiExperimentalScheduler.resetWeight": "重置窗口",
|
||||
"admin.settings.openaiExperimentalScheduler.quotaHeadroomWeight": "额度余量",
|
||||
"admin.settings.openaiExperimentalScheduler.upstreamCostWeight": "计费倍率",
|
||||
"admin.settings.openaiExperimentalScheduler.previousResponseWeight": "previous_response 粘性",
|
||||
"admin.settings.openaiExperimentalScheduler.sessionStickyWeight": "session_hash 粘性",
|
||||
"admin.settings.site.uploadImage": "上传图片",
|
||||
@@ -428,6 +434,8 @@ const baseSettingsResponse = {
|
||||
payment_visible_method_wxpay_source: "invalid-source",
|
||||
payment_visible_method_alipay_enabled: true,
|
||||
payment_visible_method_wxpay_enabled: true,
|
||||
openai_low_upstream_rate_priority_enabled: false,
|
||||
openai_oauth_scheduling_rate_multiplier: 1,
|
||||
openai_advanced_scheduler_enabled: false,
|
||||
openai_advanced_scheduler_sticky_weighted_enabled: false,
|
||||
openai_advanced_scheduler_subscription_priority_enabled: false,
|
||||
@@ -439,6 +447,7 @@ const baseSettingsResponse = {
|
||||
openai_advanced_scheduler_weight_ttft: "",
|
||||
openai_advanced_scheduler_weight_reset: "",
|
||||
openai_advanced_scheduler_weight_quota_headroom: "",
|
||||
openai_advanced_scheduler_weight_upstream_cost: "",
|
||||
openai_advanced_scheduler_weight_previous_response: "",
|
||||
openai_advanced_scheduler_weight_session_sticky: "",
|
||||
openai_advanced_scheduler_effective_lb_top_k: "7",
|
||||
@@ -449,6 +458,7 @@ const baseSettingsResponse = {
|
||||
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_upstream_cost: "0",
|
||||
openai_advanced_scheduler_effective_weight_previous_response: "5",
|
||||
openai_advanced_scheduler_effective_weight_session_sticky: "3",
|
||||
balance_low_notify_enabled: false,
|
||||
@@ -838,6 +848,66 @@ describe("admin SettingsView payment visible method controls", () => {
|
||||
expect(wrapper.text()).not.toContain("OpenAI 高级调度器");
|
||||
});
|
||||
|
||||
it("places and explains rate controls for both scheduling modes", async () => {
|
||||
const wrapper = mountView();
|
||||
|
||||
await flushPromises();
|
||||
expect(
|
||||
wrapper.find('[data-testid="openai-oauth-scheduling-rate-multiplier"]').exists(),
|
||||
).toBe(false);
|
||||
|
||||
const lowRateToggle = wrapper.get('[data-testid="openai-low-rate-priority-toggle"]');
|
||||
await lowRateToggle.setValue(true);
|
||||
const priorityModeText = wrapper.text();
|
||||
expect(priorityModeText).toContain(
|
||||
"同一分组同时包含 API Key 和 OAuth 账号时,OAuth 账号按此倍率与已探测的 API Key 计费倍率一起排序。",
|
||||
);
|
||||
expect(priorityModeText.indexOf("低倍率优先")).toBeLessThan(
|
||||
priorityModeText.indexOf("OAuth 调度参考倍率"),
|
||||
);
|
||||
expect(priorityModeText.indexOf("OAuth 调度参考倍率")).toBeLessThan(
|
||||
priorityModeText.indexOf("OpenAI 实验调度策略"),
|
||||
);
|
||||
|
||||
const oauthRateInput = wrapper.get(
|
||||
'[data-testid="openai-oauth-scheduling-rate-multiplier"]',
|
||||
);
|
||||
await oauthRateInput.setValue("0.05");
|
||||
await wrapper.find("form").trigger("submit.prevent");
|
||||
await flushPromises();
|
||||
|
||||
expect(updateSettings).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
openai_low_upstream_rate_priority_enabled: true,
|
||||
openai_oauth_scheduling_rate_multiplier: 0.05,
|
||||
}),
|
||||
);
|
||||
|
||||
await wrapper
|
||||
.get('[data-testid="openai-advanced-scheduler-toggle"]')
|
||||
.setValue(true);
|
||||
expect(
|
||||
wrapper.find('[data-testid="openai-low-rate-priority-toggle"]').exists(),
|
||||
).toBe(false);
|
||||
expect(
|
||||
wrapper.find('[data-testid="openai-oauth-scheduling-rate-multiplier"]').exists(),
|
||||
).toBe(true);
|
||||
const weightedModeText = wrapper.text();
|
||||
expect(weightedModeText).toContain(
|
||||
"同一分组同时包含 API Key 和 OAuth 账号时,计算“计费倍率”得分时,OAuth 账号按此倍率参与计算。",
|
||||
);
|
||||
expect(weightedModeText).not.toContain(
|
||||
"OAuth 账号按此倍率与已探测的 API Key 计费倍率一起排序。",
|
||||
);
|
||||
expect(weightedModeText.indexOf("订阅优先")).toBeLessThan(
|
||||
weightedModeText.indexOf("OAuth 调度参考倍率"),
|
||||
);
|
||||
expect(weightedModeText.indexOf("OAuth 调度参考倍率")).toBeLessThan(
|
||||
weightedModeText.indexOf("调度权值覆盖"),
|
||||
);
|
||||
expect(weightedModeText).toContain("计费倍率");
|
||||
});
|
||||
|
||||
it("passes translated upload and remove labels to the payment help image uploader", async () => {
|
||||
const wrapper = mountView();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user