mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-09 18:59:50 +08:00
fix: preserve provider auth metadata
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<Dialog
|
||||
:model-value="modelValue"
|
||||
title="高级设置"
|
||||
description="冷却、健康、成本控制与其他高级参数"
|
||||
description="冷却、热池与其他高级参数"
|
||||
size="3xl"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
>
|
||||
@@ -11,18 +11,18 @@
|
||||
<div class="space-y-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h3 class="text-sm font-semibold">
|
||||
冷却与健康
|
||||
冷却与热池
|
||||
</h3>
|
||||
<span class="rounded-full bg-muted px-2 py-0.5 text-[11px] text-muted-foreground">
|
||||
核心策略
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs leading-5 text-muted-foreground">
|
||||
控制自动冷却、自适应热池、异常清理和全局调度优先级。
|
||||
控制冷却时间、自适应热池和异常清理。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3 lg:grid-cols-3">
|
||||
<div class="grid gap-3 lg:grid-cols-2">
|
||||
<div
|
||||
v-for="item in healthToggleCards"
|
||||
:key="item.key"
|
||||
@@ -134,175 +134,83 @@
|
||||
@update:model-value="(v) => form.overload_cooldown_seconds = parseNum(v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="space-y-4 rounded-2xl border border-border/60 bg-card/70 p-4 sm:p-5">
|
||||
<div class="space-y-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h3 class="text-sm font-semibold">
|
||||
批量操作
|
||||
</h3>
|
||||
<span class="rounded-full bg-muted px-2 py-0.5 text-[11px] text-muted-foreground">
|
||||
任务效率
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs leading-5 text-muted-foreground">
|
||||
控制刷新 OAuth、自适应热池和批量额度处理时的并行请求数。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3 rounded-xl bg-muted/30 p-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
粘性会话 TTL
|
||||
<span class="text-xs text-muted-foreground">(秒)</span>
|
||||
并发数
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.sticky_session_ttl_seconds ?? ''"
|
||||
:model-value="form.batch_concurrency ?? ''"
|
||||
type="number"
|
||||
min="60"
|
||||
max="86400"
|
||||
placeholder="3600 (留空禁用)"
|
||||
@update:model-value="(v) => form.sticky_session_ttl_seconds = parseNum(v)"
|
||||
min="1"
|
||||
max="32"
|
||||
placeholder="8"
|
||||
@update:model-value="(v) => form.batch_concurrency = parseNum(v)"
|
||||
/>
|
||||
<p class="text-[11px] leading-5 text-muted-foreground">
|
||||
为空时沿用默认值;数值越大,批量操作越快,但会增加瞬时请求压力。
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
探测并发
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.probe_concurrency ?? ''"
|
||||
type="number"
|
||||
min="1"
|
||||
max="64"
|
||||
placeholder="4"
|
||||
@update:model-value="(v) => form.probe_concurrency = parseNum(v)"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
全局优先级
|
||||
评分 Top-N
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.global_priority ?? ''"
|
||||
:model-value="form.score_top_n ?? ''"
|
||||
type="number"
|
||||
min="0"
|
||||
max="999999"
|
||||
placeholder="留空回退 provider_priority"
|
||||
@update:model-value="(v) => form.global_priority = parseNum(v)"
|
||||
min="1"
|
||||
max="4096"
|
||||
placeholder="128"
|
||||
@update:model-value="(v) => form.score_top_n = parseNum(v)"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
回退扫描
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.score_fallback_scan_limit ?? ''"
|
||||
type="number"
|
||||
min="1"
|
||||
max="100000"
|
||||
placeholder="1024"
|
||||
@update:model-value="(v) => form.score_fallback_scan_limit = parseNum(v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div :class="secondarySectionLayout.wrapperClass">
|
||||
<section class="space-y-4 rounded-2xl border border-border/60 bg-card/70 p-4 sm:p-5">
|
||||
<div class="space-y-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h3 class="text-sm font-semibold">
|
||||
成本控制
|
||||
</h3>
|
||||
<span class="rounded-full bg-muted px-2 py-0.5 text-[11px] text-muted-foreground">
|
||||
额度保护
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs leading-5 text-muted-foreground">
|
||||
控制窗口期、Key 限额与软阈值,防止个别账号短时间内过度消耗。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid gap-3 sm:grid-cols-2"
|
||||
:class="costFieldLayout.desktopColumnsClass"
|
||||
>
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
成本窗口
|
||||
<span class="text-xs text-muted-foreground">(秒)</span>
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.cost_window_seconds ?? ''"
|
||||
type="number"
|
||||
min="3600"
|
||||
max="86400"
|
||||
placeholder="18000 (5 小时)"
|
||||
@update:model-value="(v) => form.cost_window_seconds = parseNum(v)"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
Key 窗口限额
|
||||
<span class="text-xs text-muted-foreground">(tokens)</span>
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.cost_limit_per_key_tokens ?? ''"
|
||||
type="number"
|
||||
min="0"
|
||||
placeholder="留空 = 不限"
|
||||
@update:model-value="(v) => form.cost_limit_per_key_tokens = parseNum(v)"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
软阈值
|
||||
<span class="text-xs text-muted-foreground">(%)</span>
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.cost_soft_threshold_percent ?? ''"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
placeholder="80"
|
||||
@update:model-value="(v) => form.cost_soft_threshold_percent = parseNum(v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="space-y-4 rounded-2xl border border-border/60 bg-card/70 p-4 sm:p-5">
|
||||
<div class="space-y-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h3 class="text-sm font-semibold">
|
||||
批量操作
|
||||
</h3>
|
||||
<span class="rounded-full bg-muted px-2 py-0.5 text-[11px] text-muted-foreground">
|
||||
任务效率
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-xs leading-5 text-muted-foreground">
|
||||
控制刷新 OAuth、自适应热池和批量额度处理时的并行请求数。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3 rounded-xl bg-muted/30 p-4 sm:grid-cols-2">
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
并发数
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.batch_concurrency ?? ''"
|
||||
type="number"
|
||||
min="1"
|
||||
max="32"
|
||||
placeholder="8"
|
||||
@update:model-value="(v) => form.batch_concurrency = parseNum(v)"
|
||||
/>
|
||||
<p class="text-[11px] leading-5 text-muted-foreground">
|
||||
为空时沿用默认值;数值越大,批量操作越快,但会增加瞬时请求压力。
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
探测并发
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.probe_concurrency ?? ''"
|
||||
type="number"
|
||||
min="1"
|
||||
max="64"
|
||||
placeholder="4"
|
||||
@update:model-value="(v) => form.probe_concurrency = parseNum(v)"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
评分 Top-N
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.score_top_n ?? ''"
|
||||
type="number"
|
||||
min="1"
|
||||
max="4096"
|
||||
placeholder="128"
|
||||
@update:model-value="(v) => form.score_top_n = parseNum(v)"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label>
|
||||
回退扫描
|
||||
</Label>
|
||||
<Input
|
||||
:model-value="form.score_fallback_scan_limit ?? ''"
|
||||
type="number"
|
||||
min="1"
|
||||
max="100000"
|
||||
placeholder="1024"
|
||||
@update:model-value="(v) => form.score_fallback_scan_limit = parseNum(v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section class="space-y-4 rounded-2xl border border-border/60 bg-card/70 p-4 sm:p-5">
|
||||
<div class="space-y-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@@ -625,9 +533,7 @@ import { parseApiError } from '@/utils/errorParser'
|
||||
import { updateProvider } from '@/api/endpoints'
|
||||
import {
|
||||
buildPoolCooldownFieldLayout,
|
||||
buildPoolCostFieldLayout,
|
||||
buildPoolHealthToggleCards,
|
||||
buildPoolSecondarySectionLayout,
|
||||
type PoolHealthToggleKey,
|
||||
} from '@/features/pool/utils/poolAdvancedDialog'
|
||||
import type {
|
||||
@@ -658,18 +564,10 @@ const isClaudeCode = computed(() => {
|
||||
|
||||
const healthToggleCards = buildPoolHealthToggleCards()
|
||||
const cooldownFieldLayout = buildPoolCooldownFieldLayout()
|
||||
const costFieldLayout = buildPoolCostFieldLayout()
|
||||
const secondarySectionLayout = buildPoolSecondarySectionLayout()
|
||||
|
||||
const form = ref({
|
||||
global_priority: null as number | null | undefined,
|
||||
sticky_session_ttl_seconds: null as number | null | undefined,
|
||||
health_policy_enabled: true,
|
||||
rate_limit_cooldown_seconds: null as number | null | undefined,
|
||||
overload_cooldown_seconds: null as number | null | undefined,
|
||||
cost_window_seconds: null as number | null | undefined,
|
||||
cost_limit_per_key_tokens: null as number | null | undefined,
|
||||
cost_soft_threshold_percent: null as number | null | undefined,
|
||||
batch_concurrency: null as number | null | undefined,
|
||||
probe_concurrency: null as number | null | undefined,
|
||||
score_top_n: null as number | null | undefined,
|
||||
@@ -721,8 +619,6 @@ function parseNum(v: string | number): number | undefined {
|
||||
|
||||
function getHealthToggleValue(key: PoolHealthToggleKey): boolean {
|
||||
switch (key) {
|
||||
case 'health_policy_enabled':
|
||||
return form.value.health_policy_enabled
|
||||
case 'probing_enabled':
|
||||
return form.value.probing_enabled
|
||||
case 'account_self_check_enabled':
|
||||
@@ -736,9 +632,6 @@ function getHealthToggleValue(key: PoolHealthToggleKey): boolean {
|
||||
|
||||
function updateHealthToggleValue(key: PoolHealthToggleKey, value: boolean): void {
|
||||
switch (key) {
|
||||
case 'health_policy_enabled':
|
||||
form.value.health_policy_enabled = value
|
||||
return
|
||||
case 'probing_enabled':
|
||||
form.value.probing_enabled = value
|
||||
return
|
||||
@@ -760,14 +653,8 @@ watch(() => props.modelValue, (open) => {
|
||||
const scoreRules = cfg?.score_rules
|
||||
const scoreWeights = scoreRules?.weights
|
||||
form.value = {
|
||||
global_priority: cfg?.global_priority ?? null,
|
||||
sticky_session_ttl_seconds: cfg?.sticky_session_ttl_seconds ?? null,
|
||||
health_policy_enabled: cfg?.health_policy_enabled !== false,
|
||||
rate_limit_cooldown_seconds: cfg?.rate_limit_cooldown_seconds ?? null,
|
||||
overload_cooldown_seconds: cfg?.overload_cooldown_seconds ?? null,
|
||||
cost_window_seconds: cfg?.cost_window_seconds ?? null,
|
||||
cost_limit_per_key_tokens: cfg?.cost_limit_per_key_tokens ?? null,
|
||||
cost_soft_threshold_percent: cfg?.cost_soft_threshold_percent ?? null,
|
||||
batch_concurrency: cfg?.batch_concurrency ?? null,
|
||||
probe_concurrency: cfg?.probe_concurrency ?? null,
|
||||
score_top_n: cfg?.score_top_n ?? null,
|
||||
@@ -836,20 +723,20 @@ async function handleSave() {
|
||||
'self_check_method',
|
||||
'account_self_check_request',
|
||||
'self_check_request',
|
||||
'health_policy_enabled',
|
||||
'sticky_session_ttl_seconds',
|
||||
'global_priority',
|
||||
'cost_window_seconds',
|
||||
'cost_limit_per_key_tokens',
|
||||
'cost_soft_threshold_percent',
|
||||
]) {
|
||||
delete existingPoolAdvanced[key]
|
||||
}
|
||||
// 合并已有配置(保留 scheduling_presets 等不在此对话框编辑的字段)
|
||||
const poolAdvanced: Record<string, unknown> = {
|
||||
...existingPoolAdvanced,
|
||||
global_priority: form.value.global_priority ?? undefined,
|
||||
sticky_session_ttl_seconds: form.value.sticky_session_ttl_seconds ?? undefined,
|
||||
cost_window_seconds: form.value.cost_window_seconds ?? undefined,
|
||||
cost_limit_per_key_tokens: form.value.cost_limit_per_key_tokens ?? undefined,
|
||||
cost_soft_threshold_percent: form.value.cost_soft_threshold_percent ?? undefined,
|
||||
rate_limit_cooldown_seconds: form.value.rate_limit_cooldown_seconds ?? undefined,
|
||||
overload_cooldown_seconds: form.value.overload_cooldown_seconds ?? undefined,
|
||||
health_policy_enabled: form.value.health_policy_enabled,
|
||||
batch_concurrency: form.value.batch_concurrency ?? undefined,
|
||||
probe_concurrency: form.value.probe_concurrency ?? undefined,
|
||||
score_top_n: form.value.score_top_n ?? undefined,
|
||||
|
||||
@@ -3,14 +3,11 @@ import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
buildPoolCooldownFieldLayout,
|
||||
buildPoolHealthToggleCards,
|
||||
buildPoolCostFieldLayout,
|
||||
buildPoolSecondarySectionLayout,
|
||||
} from '@/features/pool/utils/poolAdvancedDialog'
|
||||
|
||||
describe('poolAdvancedDialog', () => {
|
||||
it('returns health toggle cards in the desktop display order', () => {
|
||||
expect(buildPoolHealthToggleCards().map(item => item.key)).toEqual([
|
||||
'health_policy_enabled',
|
||||
'probing_enabled',
|
||||
'account_self_check_enabled',
|
||||
'auto_remove_banned_keys',
|
||||
@@ -20,11 +17,6 @@ describe('poolAdvancedDialog', () => {
|
||||
|
||||
it('provides tooltip copy for every desktop health toggle card', () => {
|
||||
expect(buildPoolHealthToggleCards()).toEqual([
|
||||
{
|
||||
key: 'health_policy_enabled',
|
||||
label: '健康策略',
|
||||
description: '按上游错误自动冷却并跳过异常账号。',
|
||||
},
|
||||
{
|
||||
key: 'probing_enabled',
|
||||
label: '自适应热池',
|
||||
@@ -48,32 +40,13 @@ describe('poolAdvancedDialog', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('returns the four cooldown-related fields in one desktop row order', () => {
|
||||
it('returns only cooldown-related fields in one desktop row order', () => {
|
||||
expect(buildPoolCooldownFieldLayout()).toEqual({
|
||||
fields: [
|
||||
'rate_limit_cooldown_seconds',
|
||||
'overload_cooldown_seconds',
|
||||
'sticky_session_ttl_seconds',
|
||||
'global_priority',
|
||||
],
|
||||
desktopColumnsClass: 'xl:grid-cols-4',
|
||||
})
|
||||
})
|
||||
|
||||
it('stacks batch and cost sections as full-width rows on desktop', () => {
|
||||
expect(buildPoolSecondarySectionLayout()).toEqual({
|
||||
wrapperClass: 'space-y-4',
|
||||
})
|
||||
})
|
||||
|
||||
it('returns the three cost fields in one desktop row order', () => {
|
||||
expect(buildPoolCostFieldLayout()).toEqual({
|
||||
fields: [
|
||||
'cost_window_seconds',
|
||||
'cost_limit_per_key_tokens',
|
||||
'cost_soft_threshold_percent',
|
||||
],
|
||||
desktopColumnsClass: 'xl:grid-cols-3',
|
||||
desktopColumnsClass: 'xl:grid-cols-2',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export type PoolHealthToggleKey =
|
||||
| 'health_policy_enabled'
|
||||
| 'probing_enabled'
|
||||
| 'account_self_check_enabled'
|
||||
| 'auto_remove_banned_keys'
|
||||
@@ -16,22 +15,8 @@ export interface PoolCooldownFieldLayout {
|
||||
desktopColumnsClass: string
|
||||
}
|
||||
|
||||
export interface PoolSecondarySectionLayout {
|
||||
wrapperClass: string
|
||||
}
|
||||
|
||||
export interface PoolCostFieldLayout {
|
||||
fields: string[]
|
||||
desktopColumnsClass: string
|
||||
}
|
||||
|
||||
export function buildPoolHealthToggleCards(): PoolHealthToggleCard[] {
|
||||
return [
|
||||
{
|
||||
key: 'health_policy_enabled',
|
||||
label: '健康策略',
|
||||
description: '按上游错误自动冷却并跳过异常账号。',
|
||||
},
|
||||
{
|
||||
key: 'probing_enabled',
|
||||
label: '自适应热池',
|
||||
@@ -60,26 +45,7 @@ export function buildPoolCooldownFieldLayout(): PoolCooldownFieldLayout {
|
||||
fields: [
|
||||
'rate_limit_cooldown_seconds',
|
||||
'overload_cooldown_seconds',
|
||||
'sticky_session_ttl_seconds',
|
||||
'global_priority',
|
||||
],
|
||||
desktopColumnsClass: 'xl:grid-cols-4',
|
||||
}
|
||||
}
|
||||
|
||||
export function buildPoolSecondarySectionLayout(): PoolSecondarySectionLayout {
|
||||
return {
|
||||
wrapperClass: 'space-y-4',
|
||||
}
|
||||
}
|
||||
|
||||
export function buildPoolCostFieldLayout(): PoolCostFieldLayout {
|
||||
return {
|
||||
fields: [
|
||||
'cost_window_seconds',
|
||||
'cost_limit_per_key_tokens',
|
||||
'cost_soft_threshold_percent',
|
||||
],
|
||||
desktopColumnsClass: 'xl:grid-cols-3',
|
||||
desktopColumnsClass: 'xl:grid-cols-2',
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user