mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-12 14:10:19 +08:00
Merge remote-tracking branch 'origin/main' into codex/fix-antigravity-quota
This commit is contained in:
@@ -155,17 +155,6 @@
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label>{{ legacyT('最大重试次数') }}</Label>
|
||||
<Input
|
||||
:model-value="form.max_retries ?? ''"
|
||||
type="number"
|
||||
min="0"
|
||||
max="999"
|
||||
:placeholder="legacyT('默认 2')"
|
||||
@update:model-value="(v) => form.max_retries = parseNumberInput(v)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 超时配置 -->
|
||||
|
||||
@@ -12,12 +12,15 @@ import {
|
||||
getModelScheduling,
|
||||
modelSchedulingRuleId,
|
||||
normalizeRoutingGroupConfig,
|
||||
normalizeStickyKeyAttempts,
|
||||
parseAllowedModelsInput,
|
||||
removePerModelRoutingConfig,
|
||||
resolveModelKeyPriorityOverride,
|
||||
routingModelScopeLabel,
|
||||
savePerModelRoutingConfig,
|
||||
setDefaultPoolPriorityOverrides,
|
||||
setDefaultProviderPriorityOverrides,
|
||||
setModelKeyPriorityOverridesForFormat,
|
||||
setRoutingSortingScope,
|
||||
updateAllowedModelsFromInput,
|
||||
upsertModelSchedulingRule,
|
||||
@@ -75,6 +78,65 @@ describe('routingPolicy', () => {
|
||||
expect(policy.key_priority_overrides).toEqual({})
|
||||
})
|
||||
|
||||
it('defaults sticky key attempts to 2 and normalizes invalid values', () => {
|
||||
expect(createEmptyRoutingGroupConfig().default_policy.sticky_key_attempts).toBe(2)
|
||||
expect(normalizeRoutingGroupConfig({}).default_policy.sticky_key_attempts).toBe(2)
|
||||
expect(normalizeRoutingGroupConfig({
|
||||
default_policy: { priority_mode: 'provider', scheduling_mode: 'cache_affinity', keep_priority_on_conversion: false, sticky_key_attempts: 3 },
|
||||
}).default_policy.sticky_key_attempts).toBe(3)
|
||||
expect(normalizeStickyKeyAttempts('5')).toBe(5)
|
||||
expect(normalizeStickyKeyAttempts(-1)).toBe(2)
|
||||
expect(normalizeStickyKeyAttempts('abc')).toBe(2)
|
||||
expect(normalizeStickyKeyAttempts(500)).toBe(99)
|
||||
expect(getModelScheduling(createEmptyRoutingGroupConfig(), 'gpt-5').sticky_key_attempts).toBe(2)
|
||||
})
|
||||
|
||||
it('keeps key priority overrides independent per api format', () => {
|
||||
let config = setModelKeyPriorityOverridesForFormat(
|
||||
createEmptyRoutingGroupConfig(),
|
||||
DEFAULT_ROUTING_POLICY_MODEL,
|
||||
'OpenAI:Chat',
|
||||
{ 'key-a': 0, 'key-b': 1 },
|
||||
)
|
||||
config = setModelKeyPriorityOverridesForFormat(
|
||||
config,
|
||||
DEFAULT_ROUTING_POLICY_MODEL,
|
||||
'claude:messages',
|
||||
{ 'key-a': 3 },
|
||||
)
|
||||
|
||||
const policy = getDefaultModelPolicy(config)
|
||||
expect(policy.key_priority_overrides).toEqual({})
|
||||
expect(policy.key_priority_overrides_by_format).toEqual({
|
||||
'openai:chat': { 'key-a': 0, 'key-b': 1 },
|
||||
'claude:messages': { 'key-a': 3 },
|
||||
})
|
||||
expect(resolveModelKeyPriorityOverride(config, DEFAULT_ROUTING_POLICY_MODEL, 'openai:chat', 'key-a')).toBe(0)
|
||||
expect(resolveModelKeyPriorityOverride(config, DEFAULT_ROUTING_POLICY_MODEL, 'claude:messages', 'key-a')).toBe(3)
|
||||
expect(resolveModelKeyPriorityOverride(config, DEFAULT_ROUTING_POLICY_MODEL, 'claude:messages', 'key-b')).toBeUndefined()
|
||||
|
||||
const cleared = setModelKeyPriorityOverridesForFormat(config, DEFAULT_ROUTING_POLICY_MODEL, 'claude:messages', {})
|
||||
expect(getDefaultModelPolicy(cleared).key_priority_overrides_by_format).toEqual({
|
||||
'openai:chat': { 'key-a': 0, 'key-b': 1 },
|
||||
})
|
||||
})
|
||||
|
||||
it('falls back to format-agnostic key overrides and normalizes legacy configs', () => {
|
||||
const config = normalizeRoutingGroupConfig({
|
||||
model_policies: [{
|
||||
...createEmptyModelPolicy('gpt-5'),
|
||||
key_priority_overrides: { 'key-a': 7 },
|
||||
key_priority_overrides_by_format: { ' OpenAI:Chat ': { 'key-a': 1 } },
|
||||
}],
|
||||
})
|
||||
|
||||
expect(config.model_policies[0].key_priority_overrides_by_format).toEqual({
|
||||
'openai:chat': { 'key-a': 1 },
|
||||
})
|
||||
expect(resolveModelKeyPriorityOverride(config, 'gpt-5', 'openai:chat', 'key-a')).toBe(1)
|
||||
expect(resolveModelKeyPriorityOverride(config, 'gpt-5', 'gemini:generate_content', 'key-a')).toBe(7)
|
||||
})
|
||||
|
||||
it('stores per-model scheduling as generated routing rules', () => {
|
||||
const next = upsertModelSchedulingRule(createEmptyRoutingGroupConfig(), 'gpt-5', {
|
||||
priority_mode: 'global_key',
|
||||
|
||||
@@ -315,7 +315,8 @@ import {
|
||||
getDefaultModelPolicy,
|
||||
getModelPolicy,
|
||||
normalizeRoutingGroupConfig,
|
||||
setModelKeyPriorityOverrides,
|
||||
normalizeRoutingApiFormatKey,
|
||||
setModelKeyPriorityOverridesForFormat,
|
||||
setModelPoolPriorityOverrides,
|
||||
setModelProviderPriorityOverrides,
|
||||
type RoutingDefaultPolicy,
|
||||
@@ -452,9 +453,18 @@ const providerRows = computed<ProviderPriorityRow[]>(() => {
|
||||
.sort(comparePriorityRows)
|
||||
})
|
||||
|
||||
const selectedFormatKey = computed(() => normalizeRoutingApiFormatKey(selectedApiFormat.value))
|
||||
const selectedFormatKeyOverrides = computed<Record<string, number>>(() => (
|
||||
targetModelPolicy.value.key_priority_overrides_by_format[selectedFormatKey.value] ?? {}
|
||||
))
|
||||
|
||||
const keyRows = computed<KeyPriorityRow[]>(() => {
|
||||
const format = selectedApiFormat.value
|
||||
const keyOverrides = targetModelPolicy.value.key_priority_overrides
|
||||
// 按格式覆盖优先;旧的不分格式覆盖仅作为兜底展示
|
||||
const keyOverrides: Record<string, number> = {
|
||||
...targetModelPolicy.value.key_priority_overrides,
|
||||
...selectedFormatKeyOverrides.value,
|
||||
}
|
||||
const poolOverrides = targetModelPolicy.value.pool_priority_overrides
|
||||
const normalRows: KeyPriorityRow[] = []
|
||||
const poolGroups = new Map<string, GlobalKeySource[]>()
|
||||
@@ -686,7 +696,7 @@ function setKeyPriority(keyId: string, event: Event): void {
|
||||
})
|
||||
} else {
|
||||
updateKeyOverrides({
|
||||
...targetModelPolicy.value.key_priority_overrides,
|
||||
...selectedFormatKeyOverrides.value,
|
||||
[row.target_id]: priority,
|
||||
})
|
||||
}
|
||||
@@ -697,8 +707,14 @@ function moveKey(keyId: string, direction: -1 | 1): void {
|
||||
updateVisibleKeyAndPoolOverrides(rows)
|
||||
}
|
||||
|
||||
// Key 覆盖始终写入当前选中的 API 格式,不同格式互不影响
|
||||
function updateKeyOverrides(overrides: Record<string, number>): void {
|
||||
updateConfig(setModelKeyPriorityOverrides(config.value, targetModel.value, overrides))
|
||||
updateConfig(setModelKeyPriorityOverridesForFormat(
|
||||
config.value,
|
||||
targetModel.value,
|
||||
selectedApiFormat.value,
|
||||
overrides,
|
||||
))
|
||||
}
|
||||
|
||||
function updatePoolOverrides(overrides: Record<string, number>): void {
|
||||
@@ -710,7 +726,12 @@ function updateKeyAndPoolOverrides(
|
||||
poolOverrides: Record<string, number>,
|
||||
): void {
|
||||
const next = setModelPoolPriorityOverrides(
|
||||
setModelKeyPriorityOverrides(config.value, targetModel.value, keyOverrides),
|
||||
setModelKeyPriorityOverridesForFormat(
|
||||
config.value,
|
||||
targetModel.value,
|
||||
selectedApiFormat.value,
|
||||
keyOverrides,
|
||||
),
|
||||
targetModel.value,
|
||||
poolOverrides,
|
||||
)
|
||||
@@ -718,7 +739,7 @@ function updateKeyAndPoolOverrides(
|
||||
}
|
||||
|
||||
function updateVisibleKeyAndPoolOverrides(rows: KeyPriorityRow[]): void {
|
||||
const keyOverrides = { ...targetModelPolicy.value.key_priority_overrides }
|
||||
const keyOverrides = { ...selectedFormatKeyOverrides.value }
|
||||
const poolOverrides = { ...targetModelPolicy.value.pool_priority_overrides }
|
||||
|
||||
for (const row of keyRows.value) {
|
||||
|
||||
@@ -3,10 +3,15 @@ export type RoutingSchedulingMode = 'fixed_order' | 'cache_affinity' | 'load_bal
|
||||
export type RoutingRulePhase = 'client_request' | 'provider_request'
|
||||
export type RoutingSortingScope = 'unified' | 'per_model'
|
||||
|
||||
/** 首个候选(粘性 Key)的总尝试次数默认值:失败后同 Key 重试 1 次 */
|
||||
export const DEFAULT_STICKY_KEY_ATTEMPTS = 2
|
||||
|
||||
export interface RoutingDefaultPolicy {
|
||||
priority_mode: RoutingPriorityMode
|
||||
scheduling_mode: RoutingSchedulingMode
|
||||
keep_priority_on_conversion: boolean
|
||||
/** 首个候选的总尝试次数;后续候选始终只尝试 1 次。0 或 1 表示不重试 */
|
||||
sticky_key_attempts: number
|
||||
}
|
||||
|
||||
export interface RoutingPoolSchedulingPreset {
|
||||
@@ -25,6 +30,8 @@ export interface RoutingModelPolicy {
|
||||
allowed_keys: string[]
|
||||
provider_priority_overrides: Record<string, number>
|
||||
key_priority_overrides: Record<string, number>
|
||||
/** api_format -> key_id -> priority;同一 Key 在不同 API 格式下可独立排序 */
|
||||
key_priority_overrides_by_format: Record<string, Record<string, number>>
|
||||
pool_priority_overrides: Record<string, number>
|
||||
pool_policy_overrides: Record<string, RoutingPoolPolicyOverride>
|
||||
}
|
||||
@@ -49,6 +56,7 @@ export interface RoutingSetSchedulingAction {
|
||||
type: 'set_scheduling'
|
||||
priority_mode: RoutingPriorityMode
|
||||
scheduling_mode: RoutingSchedulingMode
|
||||
sticky_key_attempts?: number
|
||||
}
|
||||
|
||||
export interface RoutingGroupConfig {
|
||||
@@ -68,12 +76,19 @@ export function createEmptyRoutingGroupConfig(): RoutingGroupConfig {
|
||||
priority_mode: 'provider',
|
||||
scheduling_mode: 'cache_affinity',
|
||||
keep_priority_on_conversion: false,
|
||||
sticky_key_attempts: DEFAULT_STICKY_KEY_ATTEMPTS,
|
||||
},
|
||||
model_policies: [],
|
||||
rules: [],
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeStickyKeyAttempts(value: unknown): number {
|
||||
const parsed = Math.trunc(Number(value))
|
||||
if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_STICKY_KEY_ATTEMPTS
|
||||
return Math.min(parsed, 99)
|
||||
}
|
||||
|
||||
export function createEmptyModelPolicy(model = ''): RoutingModelPolicy {
|
||||
return {
|
||||
model,
|
||||
@@ -81,6 +96,7 @@ export function createEmptyModelPolicy(model = ''): RoutingModelPolicy {
|
||||
allowed_keys: [],
|
||||
provider_priority_overrides: {},
|
||||
key_priority_overrides: {},
|
||||
key_priority_overrides_by_format: {},
|
||||
pool_priority_overrides: {},
|
||||
pool_policy_overrides: {},
|
||||
}
|
||||
@@ -94,6 +110,9 @@ export function normalizeRoutingGroupConfig(value: Partial<RoutingGroupConfig> |
|
||||
default_policy: {
|
||||
...base.default_policy,
|
||||
...(value?.default_policy ?? {}),
|
||||
sticky_key_attempts: normalizeStickyKeyAttempts(
|
||||
value?.default_policy?.sticky_key_attempts ?? DEFAULT_STICKY_KEY_ATTEMPTS,
|
||||
),
|
||||
},
|
||||
model_policies: Array.isArray(value?.model_policies)
|
||||
? value.model_policies.map(policy => ({
|
||||
@@ -103,6 +122,9 @@ export function normalizeRoutingGroupConfig(value: Partial<RoutingGroupConfig> |
|
||||
allowed_keys: Array.isArray(policy.allowed_keys) ? [...policy.allowed_keys] : [],
|
||||
provider_priority_overrides: { ...(policy.provider_priority_overrides ?? {}) },
|
||||
key_priority_overrides: { ...(policy.key_priority_overrides ?? {}) },
|
||||
key_priority_overrides_by_format: normalizeKeyPriorityOverridesByFormat(
|
||||
policy.key_priority_overrides_by_format,
|
||||
),
|
||||
pool_priority_overrides: { ...(policy.pool_priority_overrides ?? {}) },
|
||||
pool_policy_overrides: { ...(policy.pool_policy_overrides ?? {}) },
|
||||
}))
|
||||
@@ -296,6 +318,64 @@ export function setModelKeyPriorityOverrides(
|
||||
})
|
||||
}
|
||||
|
||||
export function normalizeRoutingApiFormatKey(apiFormat: string): string {
|
||||
return apiFormat.trim().toLowerCase()
|
||||
}
|
||||
|
||||
export function getModelKeyPriorityOverridesForFormat(
|
||||
config: RoutingGroupConfig,
|
||||
model: string,
|
||||
apiFormat: string,
|
||||
): Record<string, number> {
|
||||
const policy = getModelPolicy(config, model)
|
||||
const format = normalizeRoutingApiFormatKey(apiFormat)
|
||||
return { ...(policy.key_priority_overrides_by_format[format] ?? {}) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 某个 Key 在指定 API 格式下的生效覆盖值:按格式覆盖优先,其次是不分格式的 Key 覆盖。
|
||||
*/
|
||||
export function resolveModelKeyPriorityOverride(
|
||||
config: RoutingGroupConfig,
|
||||
model: string,
|
||||
apiFormat: string,
|
||||
keyId: string,
|
||||
): number | undefined {
|
||||
const policy = getModelPolicy(config, model)
|
||||
const format = normalizeRoutingApiFormatKey(apiFormat)
|
||||
return policy.key_priority_overrides_by_format[format]?.[keyId]
|
||||
?? policy.key_priority_overrides[keyId]
|
||||
}
|
||||
|
||||
export function setModelKeyPriorityOverridesForFormat(
|
||||
config: RoutingGroupConfig,
|
||||
model: string,
|
||||
apiFormat: string,
|
||||
overrides: Record<string, number>,
|
||||
): RoutingGroupConfig {
|
||||
const normalizedModel = model.trim() || DEFAULT_ROUTING_POLICY_MODEL
|
||||
const format = normalizeRoutingApiFormatKey(apiFormat)
|
||||
if (!format) return normalizeRoutingGroupConfig(config)
|
||||
|
||||
const current = getModelPolicy(config, normalizedModel)
|
||||
const byFormat = { ...current.key_priority_overrides_by_format }
|
||||
const normalized = normalizePriorityOverrides(overrides)
|
||||
if (Object.keys(normalized).length > 0) {
|
||||
byFormat[format] = normalized
|
||||
} else {
|
||||
delete byFormat[format]
|
||||
}
|
||||
|
||||
if (normalizedModel === DEFAULT_ROUTING_POLICY_MODEL) {
|
||||
return upsertDefaultModelPolicy(config, { key_priority_overrides_by_format: byFormat })
|
||||
}
|
||||
return upsertModelPolicy(config, {
|
||||
...current,
|
||||
model: normalizedModel,
|
||||
key_priority_overrides_by_format: byFormat,
|
||||
})
|
||||
}
|
||||
|
||||
export function setModelPoolPriorityOverrides(
|
||||
config: RoutingGroupConfig,
|
||||
model: string,
|
||||
@@ -347,6 +427,7 @@ export function getModelScheduling(
|
||||
priority_mode: action?.priority_mode ?? normalized.default_policy.priority_mode,
|
||||
scheduling_mode: action?.scheduling_mode ?? normalized.default_policy.scheduling_mode,
|
||||
keep_priority_on_conversion: normalized.default_policy.keep_priority_on_conversion,
|
||||
sticky_key_attempts: action?.sticky_key_attempts ?? normalized.default_policy.sticky_key_attempts,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,6 +545,25 @@ export function normalizePriorityOverrides(overrides: Record<string, number>): R
|
||||
return normalized
|
||||
}
|
||||
|
||||
function normalizeKeyPriorityOverridesByFormat(
|
||||
value: Record<string, Record<string, number>> | null | undefined,
|
||||
): Record<string, Record<string, number>> {
|
||||
const normalized: Record<string, Record<string, number>> = {}
|
||||
if (!value || typeof value !== 'object') return normalized
|
||||
for (const [rawFormat, overrides] of Object.entries(value)) {
|
||||
const format = normalizeRoutingApiFormatKey(rawFormat)
|
||||
if (!format || !overrides || typeof overrides !== 'object') continue
|
||||
const merged = normalizePriorityOverrides({
|
||||
...(normalized[format] ?? {}),
|
||||
...overrides,
|
||||
})
|
||||
if (Object.keys(merged).length > 0) {
|
||||
normalized[format] = merged
|
||||
}
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
function isSetSchedulingAction(action: unknown): action is RoutingSetSchedulingAction {
|
||||
if (!action || typeof action !== 'object') return false
|
||||
const candidate = action as Partial<RoutingSetSchedulingAction>
|
||||
|
||||
@@ -977,6 +977,7 @@ const MOCK_ROUTING_GROUPS: MockRoutingGroup[] = [
|
||||
priority_mode: 'provider',
|
||||
scheduling_mode: 'cache_affinity',
|
||||
keep_priority_on_conversion: false,
|
||||
sticky_key_attempts: 2,
|
||||
},
|
||||
model_policies: [
|
||||
{
|
||||
@@ -985,6 +986,8 @@ const MOCK_ROUTING_GROUPS: MockRoutingGroup[] = [
|
||||
allowed_keys: [],
|
||||
provider_priority_overrides: { 'provider-002': 0 },
|
||||
key_priority_overrides: {},
|
||||
key_priority_overrides_by_format: {},
|
||||
pool_priority_overrides: {},
|
||||
pool_policy_overrides: {},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -272,6 +272,8 @@ import {
|
||||
type ProviderWithEndpointsSummary,
|
||||
} from '@/api/endpoints'
|
||||
import { adminApi } from '@/api/admin'
|
||||
import { listRoutingGroups } from '@/api/routing-profiles'
|
||||
import { normalizeRoutingGroupConfig } from '@/features/routing/utils/routingPolicy'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
import { useI18n } from '@/i18n'
|
||||
|
||||
@@ -533,8 +535,18 @@ const maxProviderPriority = computed(() => {
|
||||
return priorities.length > 0 ? Math.max(...priorities) : undefined
|
||||
})
|
||||
|
||||
// 加载优先级模式
|
||||
// 加载优先级模式:优先使用启用中的系统默认调度策略,旧的系统配置键仅作兜底
|
||||
async function loadPriorityMode(options: { cacheTtlMs?: number } = {}) {
|
||||
try {
|
||||
const groups = await listRoutingGroups()
|
||||
const systemDefault = groups.items.find(group => group.is_system_default && group.enabled)
|
||||
if (systemDefault) {
|
||||
priorityMode.value = normalizeRoutingGroupConfig(systemDefault.config_json).default_policy.priority_mode
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// 路由策略不可用时继续尝试旧配置
|
||||
}
|
||||
try {
|
||||
const response = await adminApi.getSystemConfig('provider_priority_mode', {
|
||||
cacheTtlMs: options.cacheTtlMs ?? 0,
|
||||
|
||||
@@ -454,6 +454,44 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label
|
||||
class="flex items-start gap-3 rounded-lg border border-border/60 px-3 py-2 text-sm"
|
||||
data-testid="keep-priority-on-conversion"
|
||||
>
|
||||
<Switch
|
||||
:model-value="keepPriorityOnConversion"
|
||||
:disabled="saving"
|
||||
aria-label="格式转换时保持优先级"
|
||||
@update:model-value="updateKeepPriorityOnConversion"
|
||||
/>
|
||||
<span class="min-w-0">
|
||||
<span class="block font-medium">格式转换时保持优先级</span>
|
||||
<span class="mt-0.5 block text-xs text-muted-foreground">
|
||||
开启后,需要跨 API 格式转换的候选不会被降级到同格式候选之后。作用于本策略范围内的全部模型;Provider 自身的同名开关仍单独生效。
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="flex items-start gap-3 rounded-lg border border-border/60 px-3 py-2 text-sm"
|
||||
data-testid="sticky-key-attempts"
|
||||
>
|
||||
<Input
|
||||
:model-value="stickyKeyAttempts"
|
||||
type="number"
|
||||
min="0"
|
||||
max="99"
|
||||
class="w-20 shrink-0"
|
||||
:disabled="saving"
|
||||
aria-label="粘性 Key 尝试次数"
|
||||
@update:model-value="updateStickyKeyAttempts"
|
||||
/>
|
||||
<span class="min-w-0">
|
||||
<span class="block font-medium">粘性 Key 尝试次数</span>
|
||||
<span class="mt-0.5 block text-xs text-muted-foreground">
|
||||
首个候选(缓存亲和命中的 Key)的总尝试次数。2 表示失败后同 Key 重试 1 次再转移,避免偶发错误破坏缓存;0 或 1 表示不重试。转移后的候选始终只尝试 1 次。
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<RoutingPriorityPolicyEditor
|
||||
@@ -749,6 +787,7 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Input,
|
||||
Switch,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCard,
|
||||
@@ -762,6 +801,7 @@ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuIte
|
||||
import { AlertDialog } from '@/components/common'
|
||||
import {
|
||||
DEFAULT_ROUTING_POLICY_MODEL,
|
||||
DEFAULT_STICKY_KEY_ATTEMPTS,
|
||||
allowedModelsMirrorPerModelPolicies,
|
||||
clearAllowedModels,
|
||||
copyPerModelRoutingConfig,
|
||||
@@ -772,6 +812,7 @@ import {
|
||||
isGeneratedModelSchedulingRule,
|
||||
modelSchedulingRuleId,
|
||||
normalizeRoutingGroupConfig,
|
||||
normalizeStickyKeyAttempts,
|
||||
removePerModelRoutingConfig,
|
||||
routingModelScopeLabel,
|
||||
savePerModelRoutingConfig,
|
||||
@@ -874,6 +915,12 @@ const firstStepSchedulingMode = computed<RoutingSchedulingMode>(() => {
|
||||
}
|
||||
return draft.value?.config_json.default_policy.scheduling_mode ?? 'cache_affinity'
|
||||
})
|
||||
const keepPriorityOnConversion = computed<boolean>(() => (
|
||||
draft.value?.config_json.default_policy.keep_priority_on_conversion ?? false
|
||||
))
|
||||
const stickyKeyAttempts = computed<number>(() => (
|
||||
draft.value?.config_json.default_policy.sticky_key_attempts ?? DEFAULT_STICKY_KEY_ATTEMPTS
|
||||
))
|
||||
const allowedModelsLookLikeLegacyMirror = computed(() => {
|
||||
return draft.value
|
||||
? allowedModelsMirrorPerModelPolicies(draft.value.config_json)
|
||||
@@ -1190,6 +1237,28 @@ function updateFirstStepSchedulingMode(mode: RoutingSchedulingMode): void {
|
||||
})
|
||||
}
|
||||
|
||||
function updateStickyKeyAttempts(value: string | number): void {
|
||||
if (!draft.value) return
|
||||
updateDraftConfig({
|
||||
...draft.value.config_json,
|
||||
default_policy: {
|
||||
...draft.value.config_json.default_policy,
|
||||
sticky_key_attempts: normalizeStickyKeyAttempts(value),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function updateKeepPriorityOnConversion(value: boolean): void {
|
||||
if (!draft.value) return
|
||||
updateDraftConfig({
|
||||
...draft.value.config_json,
|
||||
default_policy: {
|
||||
...draft.value.config_json.default_policy,
|
||||
keep_priority_on_conversion: value,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function removePerModelPolicy(model: string): void {
|
||||
if (!draft.value) return
|
||||
if (perModelEditingActive.value && editingDirty.value) {
|
||||
|
||||
@@ -131,11 +131,31 @@ vi.mock('@/components/ui', async () => {
|
||||
},
|
||||
})
|
||||
|
||||
const Switch = defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
modelValue: { type: Boolean, default: false },
|
||||
disabled: Boolean,
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { attrs, emit }) {
|
||||
return () => h('button', {
|
||||
...attrs,
|
||||
type: 'button',
|
||||
role: 'switch',
|
||||
'aria-checked': props.modelValue,
|
||||
disabled: props.disabled,
|
||||
onClick: () => emit('update:modelValue', !props.modelValue),
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
Badge: wrapper(),
|
||||
Button,
|
||||
Card: wrapper('section'),
|
||||
Input,
|
||||
Switch,
|
||||
Table: wrapper('table'),
|
||||
TableBody: wrapper('tbody'),
|
||||
TableCard: wrapper(),
|
||||
@@ -195,6 +215,7 @@ function routingGroup(
|
||||
priority_mode: 'provider',
|
||||
scheduling_mode: 'cache_affinity',
|
||||
keep_priority_on_conversion: false,
|
||||
sticky_key_attempts: 2,
|
||||
},
|
||||
model_policies: [],
|
||||
rules: [],
|
||||
|
||||
Reference in New Issue
Block a user