mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-11 05:30:19 +08:00
fix(routing): decouple model overrides from allowed scope
This commit is contained in:
@@ -323,6 +323,83 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section
|
||||
class="space-y-3 rounded-lg border border-border/60 p-4"
|
||||
aria-labelledby="model-allowlist-heading"
|
||||
>
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h3
|
||||
id="model-allowlist-heading"
|
||||
class="text-sm font-medium"
|
||||
>
|
||||
模型白名单
|
||||
</h3>
|
||||
<Badge :variant="draft.config_json.allowed_models.length ? 'outline' : 'secondary'">
|
||||
{{ draft.config_json.allowed_models.length ? `${draft.config_json.allowed_models.length} 项` : '全部模型' }}
|
||||
</Badge>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-muted-foreground">
|
||||
控制此策略分组适用于哪些模型;留空表示全部模型。它与“区分模型”中的专属调度覆盖相互独立,支持精确值、* 和前缀通配符(如 gpt-*),多个值用英文逗号或换行分隔。
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
v-if="draft.config_json.allowed_models.length"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="shrink-0 text-muted-foreground hover:text-foreground"
|
||||
data-testid="clear-allowed-models"
|
||||
@click="clearAllowedModelScope"
|
||||
>
|
||||
改为全部模型
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 sm:flex-row">
|
||||
<Input
|
||||
v-model="allowedModelsInput"
|
||||
class="min-w-0 flex-1"
|
||||
data-testid="allowed-models-input"
|
||||
aria-label="模型白名单"
|
||||
placeholder="留空表示全部模型,例如:gpt-5, claude-*, legacy-model"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
class="shrink-0"
|
||||
data-testid="apply-allowed-models"
|
||||
@click="applyAllowedModelScope"
|
||||
>
|
||||
应用范围
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="draft.config_json.allowed_models.length"
|
||||
class="flex flex-wrap gap-2"
|
||||
data-testid="allowed-model-values"
|
||||
>
|
||||
<Badge
|
||||
v-for="(model, index) in draft.config_json.allowed_models"
|
||||
:key="`${model}:${index}`"
|
||||
variant="outline"
|
||||
class="font-mono font-normal"
|
||||
>
|
||||
{{ model }}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="allowedModelsLookLikeLegacyMirror"
|
||||
class="rounded-md border border-amber-500/30 bg-amber-500/5 px-3 py-2 text-xs text-muted-foreground"
|
||||
data-testid="allowed-models-legacy-mirror"
|
||||
>
|
||||
当前白名单与按模型策略列表一致,可能来自旧版界面的联动保存。现有范围会原样保留;如需让其他模型也使用默认策略,请显式点击“改为全部模型”。
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="sortingScope === 'unified'"
|
||||
class="space-y-4"
|
||||
@@ -667,21 +744,26 @@ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuIte
|
||||
import { AlertDialog } from '@/components/common'
|
||||
import {
|
||||
DEFAULT_ROUTING_POLICY_MODEL,
|
||||
allowedModelsMirrorPerModelPolicies,
|
||||
clearAllowedModels,
|
||||
copyPerModelRoutingConfig,
|
||||
createEmptyModelPolicy,
|
||||
createEmptyRoutingGroupConfig,
|
||||
getModelPolicy,
|
||||
formatAllowedModelsInput,
|
||||
getModelScheduling,
|
||||
isGeneratedModelSchedulingRule,
|
||||
modelSchedulingRuleId,
|
||||
normalizeRoutingGroupConfig,
|
||||
removeGeneratedModelSchedulingRules,
|
||||
removeModelPolicy,
|
||||
removeModelSchedulingRule,
|
||||
upsertModelPolicy,
|
||||
removePerModelRoutingConfig,
|
||||
routingModelScopeLabel,
|
||||
savePerModelRoutingConfig,
|
||||
setRoutingSortingScope,
|
||||
updateAllowedModelsFromInput,
|
||||
upsertModelSchedulingRule,
|
||||
type RoutingGroupConfig,
|
||||
type RoutingPriorityMode,
|
||||
type RoutingSchedulingMode,
|
||||
type RoutingSortingScope,
|
||||
} from '@/features/routing/utils/routingPolicy'
|
||||
import { RoutingPriorityPolicyEditor } from '@/features/routing/components'
|
||||
import {
|
||||
@@ -707,7 +789,6 @@ interface RoutingGroupDraft {
|
||||
updated_at?: number | null
|
||||
}
|
||||
|
||||
type SortingScope = 'unified' | 'per_model'
|
||||
type ModelFilter = 'configured' | 'unconfigured'
|
||||
|
||||
const modelFilters: Array<{ value: ModelFilter; label: string }> = [
|
||||
@@ -729,9 +810,10 @@ const groups = ref<RoutingGroupRecord[]>([])
|
||||
const selectedGroupId = ref<string | null>(null)
|
||||
const draft = ref<RoutingGroupDraft | null>(null)
|
||||
const savedDraftSnapshot = ref<string | null>(null)
|
||||
const sortingScope = ref<SortingScope>('unified')
|
||||
const sortingScope = ref<RoutingSortingScope>('unified')
|
||||
const selectedPerModelName = ref<string | null>(null)
|
||||
const editingConfig = ref<RoutingGroupConfig | null>(null)
|
||||
const allowedModelsInput = ref('')
|
||||
const globalModelSearch = ref('')
|
||||
const modelFilter = ref<ModelFilter>('unconfigured')
|
||||
const globalModels = ref<GlobalModelResponse[]>([])
|
||||
@@ -773,6 +855,11 @@ const firstStepSchedulingMode = computed<RoutingSchedulingMode>(() => {
|
||||
}
|
||||
return draft.value?.config_json.default_policy.scheduling_mode ?? 'cache_affinity'
|
||||
})
|
||||
const allowedModelsLookLikeLegacyMirror = computed(() => {
|
||||
return draft.value
|
||||
? allowedModelsMirrorPerModelPolicies(draft.value.config_json)
|
||||
: false
|
||||
})
|
||||
|
||||
interface ModelRow {
|
||||
name: string
|
||||
@@ -869,6 +956,7 @@ function clearDraftState(): void {
|
||||
savedDraftSnapshot.value = null
|
||||
selectedPerModelName.value = null
|
||||
editingConfig.value = null
|
||||
allowedModelsInput.value = ''
|
||||
switchModelTarget.value = null
|
||||
switchModelDialogOpen.value = false
|
||||
deleteDialogOpen.value = false
|
||||
@@ -879,6 +967,7 @@ function selectGroup(group: RoutingGroupRecord): void {
|
||||
isCreating.value = false
|
||||
selectedGroupId.value = normalized.id
|
||||
draft.value = buildDraft(normalized)
|
||||
allowedModelsInput.value = formatAllowedModelsInput(draft.value.config_json.allowed_models)
|
||||
savedDraftSnapshot.value = draftSnapshotValue(draft.value)
|
||||
syncEditorStateFromConfig(draft.value.config_json)
|
||||
resetEditingConfig()
|
||||
@@ -902,6 +991,7 @@ function startCreate(): void {
|
||||
updated_at: null,
|
||||
}
|
||||
savedDraftSnapshot.value = null
|
||||
allowedModelsInput.value = ''
|
||||
syncEditorStateFromConfig(draft.value.config_json)
|
||||
resetEditingConfig()
|
||||
}
|
||||
@@ -958,13 +1048,7 @@ function groupSortingScopeLabel(group: RoutingGroupRecord): string {
|
||||
}
|
||||
|
||||
function groupModelScopeLabel(group: RoutingGroupRecord): string {
|
||||
const config = normalizeRoutingGroupConfig(group.config_json)
|
||||
if (hasPerModelSorting(config)) {
|
||||
const count = config.model_policies.filter(policy => policy.model !== DEFAULT_ROUTING_POLICY_MODEL).length
|
||||
|| config.allowed_models.length
|
||||
return count ? `${count} 个模型` : '未选择模型'
|
||||
}
|
||||
return config.allowed_models.length ? `${config.allowed_models.length} 个模型` : '全部模型'
|
||||
return routingModelScopeLabel(group.config_json)
|
||||
}
|
||||
|
||||
function groupSchedulingSummary(group: RoutingGroupRecord): string {
|
||||
@@ -1042,13 +1126,11 @@ function hasPerModelSorting(config: RoutingGroupConfig): boolean {
|
||||
|| config.rules.some(isGeneratedModelSchedulingRule)
|
||||
}
|
||||
|
||||
function setSortingScope(scope: SortingScope): void {
|
||||
function setSortingScope(scope: RoutingSortingScope): void {
|
||||
if (!draft.value) return
|
||||
sortingScope.value = scope
|
||||
if (scope === 'unified') {
|
||||
const next = removeGeneratedModelSchedulingRules(draft.value.config_json)
|
||||
next.model_policies = next.model_policies.filter(policy => policy.model === DEFAULT_ROUTING_POLICY_MODEL)
|
||||
next.allowed_models = []
|
||||
const next = setRoutingSortingScope(draft.value.config_json, scope)
|
||||
updateDraftConfig(next)
|
||||
resetEditingConfig()
|
||||
return
|
||||
@@ -1092,9 +1174,7 @@ function removePerModelPolicy(model: string): void {
|
||||
showError('请先保存当前改动后再移除模型')
|
||||
return
|
||||
}
|
||||
let next = removeModelPolicy(draft.value.config_json, model)
|
||||
next = removeModelSchedulingRule(next, model)
|
||||
next.allowed_models = next.allowed_models.filter(item => item !== model)
|
||||
const next = removePerModelRoutingConfig(draft.value.config_json, model)
|
||||
if (selectedPerModelName.value === model) {
|
||||
selectedPerModelName.value = null
|
||||
}
|
||||
@@ -1157,19 +1237,12 @@ function copyModelConfig(sourceModel: string): void {
|
||||
if (!draft.value || !editingConfig.value) return
|
||||
const target = selectedPerModelName.value
|
||||
if (!target || target === sourceModel) return
|
||||
const sourcePolicy = getModelPolicy(draft.value.config_json, sourceModel)
|
||||
const sourceScheduling = getModelScheduling(draft.value.config_json, sourceModel)
|
||||
let next = upsertModelPolicy(editingConfig.value, {
|
||||
...sourcePolicy,
|
||||
model: target,
|
||||
})
|
||||
next = upsertModelSchedulingRule(next, target, {
|
||||
priority_mode: sourceScheduling.priority_mode,
|
||||
scheduling_mode: sourceScheduling.scheduling_mode,
|
||||
})
|
||||
if (!next.allowed_models.includes(target)) {
|
||||
next = { ...next, allowed_models: [...next.allowed_models, target] }
|
||||
}
|
||||
const next = copyPerModelRoutingConfig(
|
||||
editingConfig.value,
|
||||
draft.value.config_json,
|
||||
sourceModel,
|
||||
target,
|
||||
)
|
||||
updateEditingConfig(next)
|
||||
success(`已加载 ${globalModelLabel(sourceModel)} 的配置,点击保存生效`)
|
||||
}
|
||||
@@ -1236,6 +1309,26 @@ function globalModelLabel(modelName: string): string {
|
||||
return `${model.display_name} (${model.name})`
|
||||
}
|
||||
|
||||
function applyAllowedModelScope(): void {
|
||||
if (!draft.value) return
|
||||
const next = updateAllowedModelsFromInput(draft.value.config_json, allowedModelsInput.value)
|
||||
updateDraftConfig(next)
|
||||
if (editingConfig.value) {
|
||||
editingConfig.value = updateAllowedModelsFromInput(editingConfig.value, allowedModelsInput.value)
|
||||
}
|
||||
allowedModelsInput.value = formatAllowedModelsInput(next.allowed_models)
|
||||
}
|
||||
|
||||
function clearAllowedModelScope(): void {
|
||||
if (!draft.value) return
|
||||
const next = clearAllowedModels(draft.value.config_json)
|
||||
updateDraftConfig(next)
|
||||
if (editingConfig.value) {
|
||||
editingConfig.value = clearAllowedModels(editingConfig.value)
|
||||
}
|
||||
allowedModelsInput.value = ''
|
||||
}
|
||||
|
||||
function replaceGroup(group: RoutingGroupRecord): void {
|
||||
const normalized = normalizeRecord(group)
|
||||
const index = groups.value.findIndex(item => item.id === normalized.id)
|
||||
@@ -1326,13 +1419,7 @@ function saveCurrentModel(): void {
|
||||
showError('请先选择模型')
|
||||
return
|
||||
}
|
||||
let next = editingConfig.value
|
||||
if (!next.model_policies.some(policy => policy.model === model)) {
|
||||
next = upsertModelPolicy(next, createEmptyModelPolicy(model))
|
||||
}
|
||||
if (!next.allowed_models.includes(model)) {
|
||||
next = { ...next, allowed_models: [...next.allowed_models, model] }
|
||||
}
|
||||
const next = savePerModelRoutingConfig(editingConfig.value, model)
|
||||
updateDraftConfig(next)
|
||||
modelFilter.value = 'configured'
|
||||
resetEditingConfig()
|
||||
|
||||
Reference in New Issue
Block a user