mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-10 13:10:21 +08:00
fix(pool): 对齐账号批量配置语义
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:model-value="modelValue"
|
||||
title="密钥批量管理"
|
||||
title="账号批量操作"
|
||||
:description="dialogDescription"
|
||||
size="3xl"
|
||||
persistent
|
||||
@@ -372,7 +372,7 @@ const QUICK_SELECT_OPTIONS: Array<{ value: QuickSelectorValue; label: string }>
|
||||
]
|
||||
|
||||
const ACTION_OPTIONS: BatchActionOption[] = [
|
||||
{ value: 'edit_config', label: '编辑配置', hint: '统一修改支持 API、调度参数与模型权限。' },
|
||||
{ value: 'edit_config', label: '编辑配置', hint: '统一修改支持 API、调度参数与自动获取模型设置。' },
|
||||
{ value: 'refresh_quota', label: '刷新额度', hint: '调用额度刷新接口,适合核对最新配额状态。' },
|
||||
{ value: 'refresh_oauth', label: '刷新 OAuth', hint: '仅对 OAuth 账号有效,非 OAuth 账号会自动跳过。' },
|
||||
{ value: 'set_proxy', label: '配置代理', hint: '为选中账号绑定独立代理节点。' },
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
:model-value="open"
|
||||
title="批量编辑密钥"
|
||||
:description="dialogDescription"
|
||||
:icon="ListChecks"
|
||||
:icon="SquarePen"
|
||||
size="3xl"
|
||||
persistent
|
||||
@update:model-value="handleDialogUpdate"
|
||||
@@ -14,7 +14,7 @@
|
||||
密钥配置
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="models">
|
||||
模型权限
|
||||
可用模型范围
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
@@ -156,6 +156,57 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="space-y-3 py-4">
|
||||
<BatchFieldToggle
|
||||
v-model="form.applyAutoFetchModels"
|
||||
label="应用自动获取设置"
|
||||
>
|
||||
<div class="space-y-3 rounded-md border border-border/60 bg-muted/30 px-3 py-3">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="space-y-0.5">
|
||||
<Label class="text-sm font-medium">自动获取上游可用模型</Label>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
定时更新上游模型,配合模型映射使用
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
v-model="form.autoFetchModels"
|
||||
:disabled="!form.applyAutoFetchModels"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.autoFetchModels"
|
||||
class="space-y-2 border-t border-border/40 pt-3"
|
||||
>
|
||||
<div class="grid gap-3 sm:grid-cols-2">
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">包含规则</Label>
|
||||
<Input
|
||||
v-model="form.includePatterns"
|
||||
placeholder="gpt-*, claude-*, 留空包含全部"
|
||||
class="h-9"
|
||||
:disabled="!form.applyAutoFetchModels"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">排除规则</Label>
|
||||
<Input
|
||||
v-model="form.excludePatterns"
|
||||
placeholder="*-preview, *-beta"
|
||||
class="h-9"
|
||||
:disabled="!form.applyAutoFetchModels"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
逗号分隔,支持 * ? 通配符,不区分大小写
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</BatchFieldToggle>
|
||||
</section>
|
||||
|
||||
<section class="space-y-3 py-4">
|
||||
<label class="flex items-center gap-2 text-sm font-medium">
|
||||
<Checkbox v-model="form.applyNote" />
|
||||
@@ -176,38 +227,21 @@
|
||||
class="max-h-[min(64vh,40rem)] overflow-y-auto pr-1"
|
||||
>
|
||||
<div class="space-y-4 py-1">
|
||||
<div class="flex flex-col gap-3 border-b border-border/70 pb-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="space-y-1 border-b border-border/70 pb-4">
|
||||
<label class="flex items-center gap-2 text-sm font-medium">
|
||||
<Checkbox v-model="form.applyModels" />
|
||||
<span>批量管理模型权限</span>
|
||||
<Checkbox v-model="form.applyAllowedModels" />
|
||||
<span>应用可用模型范围</span>
|
||||
</label>
|
||||
<Select
|
||||
v-model="form.modelMode"
|
||||
:disabled="!form.applyModels"
|
||||
>
|
||||
<SelectTrigger class="h-9 w-full sm:w-48">
|
||||
<SelectValue placeholder="选择管理方式" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="manual">
|
||||
手动权限
|
||||
</SelectItem>
|
||||
<SelectItem value="automatic">
|
||||
自动发现
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p class="pl-6 text-xs text-muted-foreground">
|
||||
限制所选账号能够承接的模型;不限制时允许全部模型
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="form.modelMode"
|
||||
class="space-y-4"
|
||||
:class="!form.applyModels ? 'pointer-events-none opacity-45' : ''"
|
||||
:class="!form.applyAllowedModels ? 'pointer-events-none opacity-45' : ''"
|
||||
>
|
||||
<div
|
||||
v-if="form.modelMode === 'manual'"
|
||||
class="flex items-center justify-between gap-4 border-b border-border/70 pb-4"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-4 border-b border-border/70 pb-4">
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium">
|
||||
允许全部模型
|
||||
@@ -218,34 +252,10 @@
|
||||
</div>
|
||||
<Switch
|
||||
v-model="form.unrestrictedModels"
|
||||
:disabled="!form.applyModels"
|
||||
:disabled="!form.applyAllowedModels"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="grid gap-3 border-b border-border/70 pb-4 sm:grid-cols-2"
|
||||
>
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">包含规则</Label>
|
||||
<Input
|
||||
v-model="form.includePatterns"
|
||||
placeholder="gpt-*, claude-*"
|
||||
class="h-9"
|
||||
:disabled="!form.applyModels"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label class="text-xs">排除规则</Label>
|
||||
<Input
|
||||
v-model="form.excludePatterns"
|
||||
placeholder="*-preview, *-beta"
|
||||
class="h-9"
|
||||
:disabled="!form.applyModels"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-3"
|
||||
:class="modelSelectionDisabled ? 'pointer-events-none opacity-45' : ''"
|
||||
@@ -275,9 +285,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-3 text-xs text-muted-foreground">
|
||||
<span>
|
||||
{{ form.modelMode === 'automatic' ? '已锁定' : '已允许' }} {{ form.selectedModels.length }} 个模型
|
||||
</span>
|
||||
<span>已选择 {{ form.selectedModels.length }} 个模型</span>
|
||||
<button
|
||||
v-if="filteredModels.length > 0"
|
||||
type="button"
|
||||
@@ -334,14 +342,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="py-12 text-center text-sm text-muted-foreground"
|
||||
>
|
||||
选择模型权限管理方式后继续
|
||||
<div class="flex items-center justify-between gap-4 rounded-md border border-border/60 bg-muted/30 px-3 py-2.5">
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-medium">
|
||||
锁定已选模型
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
自动获取开启时,锁定的模型不会被同步移除
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
v-model="form.lockSelectedModels"
|
||||
:disabled="modelSelectionDisabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
@@ -390,7 +406,7 @@ import {
|
||||
TabsTrigger,
|
||||
Textarea,
|
||||
} from '@/components/ui'
|
||||
import { ListChecks, Loader2, Plus, RefreshCw, Search } from 'lucide-vue-next'
|
||||
import { Loader2, Plus, RefreshCw, Search, SquarePen } from 'lucide-vue-next'
|
||||
import { getProviderModels } from '@/api/endpoints/models'
|
||||
import { batchUpdatePoolKeys } from '@/api/endpoints/pool'
|
||||
import { formatApiFormat, sortApiFormats, type UpstreamModel } from '@/api/endpoints/types'
|
||||
@@ -452,12 +468,14 @@ function createInitialForm(): PoolKeyBatchEditState {
|
||||
maxProbeIntervalMinutes: '32',
|
||||
applyNote: false,
|
||||
note: '',
|
||||
applyModels: false,
|
||||
modelMode: '',
|
||||
unrestrictedModels: true,
|
||||
selectedModels: [],
|
||||
applyAutoFetchModels: false,
|
||||
autoFetchModels: false,
|
||||
includePatterns: '',
|
||||
excludePatterns: '',
|
||||
applyAllowedModels: false,
|
||||
unrestrictedModels: true,
|
||||
selectedModels: [],
|
||||
lockSelectedModels: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,7 +490,7 @@ const visibleApiFormats = computed(() => sortApiFormats(props.availableApiFormat
|
||||
const loadingModels = computed(() => loadingProviderModels.value || fetchingUpstreamModels.value)
|
||||
const normalizedModelSearch = computed(() => modelSearch.value.trim())
|
||||
const modelSelectionDisabled = computed(() => (
|
||||
!form.applyModels || (form.modelMode === 'manual' && form.unrestrictedModels)
|
||||
!form.applyAllowedModels || form.unrestrictedModels
|
||||
))
|
||||
const activeValue = computed({
|
||||
get: () => form.isActive ? 'enabled' : 'disabled',
|
||||
@@ -603,7 +621,7 @@ async function saveChanges(): Promise<void> {
|
||||
const build = buildPoolKeyBatchUpdatePatch(form)
|
||||
if (!build.patch || build.error) {
|
||||
warning(build.error || '批量配置无效')
|
||||
if (form.applyModels && build.error?.includes('模型')) activeTab.value = 'models'
|
||||
if (form.applyAllowedModels && build.error?.includes('模型')) activeTab.value = 'models'
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -249,13 +249,13 @@ import {
|
||||
Activity,
|
||||
ChevronDown,
|
||||
Edit,
|
||||
ListChecks,
|
||||
Plug,
|
||||
Power,
|
||||
Search,
|
||||
Settings2,
|
||||
SlidersHorizontal,
|
||||
Upload,
|
||||
Users,
|
||||
} from 'lucide-vue-next'
|
||||
import {
|
||||
Button,
|
||||
@@ -369,7 +369,7 @@ const mobileActions = computed(() => {
|
||||
{ key: 'import', title: legacyT('添加账号'), event: 'import', icon: Upload },
|
||||
{ key: 'providerProxy' },
|
||||
{ key: 'scheduling', title: legacyT('号池调度'), event: 'scheduling', icon: SlidersHorizontal },
|
||||
{ key: 'accountBatch', title: legacyT('密钥批量管理'), event: 'accountBatch', icon: ListChecks },
|
||||
{ key: 'accountBatch', title: legacyT('账号批量操作'), event: 'accountBatch', icon: Users },
|
||||
{ key: 'editProvider', title: legacyT('编辑提供商'), event: 'editProvider', icon: Edit },
|
||||
{ key: 'editEndpoint', title: legacyT('编辑端点'), event: 'editEndpoint', icon: Plug },
|
||||
]
|
||||
@@ -394,7 +394,7 @@ const desktopPostProxyActions = computed<HeaderAction[]>(() => {
|
||||
}
|
||||
actions.push(
|
||||
{ key: 'advanced', title: legacyT('高级设置'), event: 'advanced', icon: Settings2 },
|
||||
{ key: 'accountBatch', title: legacyT('密钥批量管理'), event: 'accountBatch', icon: ListChecks },
|
||||
{ key: 'accountBatch', title: legacyT('账号批量操作'), event: 'accountBatch', icon: Users },
|
||||
{ key: 'toggleProvider', title: props.providerToggleButtonTitle, event: 'toggleProvider', icon: Power },
|
||||
)
|
||||
return actions
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { createApp, nextTick } from 'vue'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import PoolKeyBatchEditDialog from '../PoolKeyBatchEditDialog.vue'
|
||||
|
||||
vi.mock('@/api/endpoints/models', () => ({
|
||||
getProviderModels: vi.fn().mockResolvedValue([]),
|
||||
}))
|
||||
|
||||
vi.mock('@/api/endpoints/pool', () => ({
|
||||
batchUpdatePoolKeys: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/features/providers/composables/useUpstreamModelsCache', () => ({
|
||||
useUpstreamModelsCache: () => ({
|
||||
fetchModelsForKeys: vi.fn().mockResolvedValue({ models: [] }),
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useConfirm', () => ({
|
||||
useConfirm: () => ({ confirm: vi.fn().mockResolvedValue(true) }),
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useToast', () => ({
|
||||
useToast: () => ({
|
||||
success: vi.fn(),
|
||||
warning: vi.fn(),
|
||||
error: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
|
||||
describe('PoolKeyBatchEditDialog', () => {
|
||||
it('uses the same automatic model discovery language as the single-key editor', async () => {
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const app = createApp(PoolKeyBatchEditDialog, {
|
||||
open: true,
|
||||
providerId: 'provider-1',
|
||||
providerName: 'Google API',
|
||||
keyIds: ['key-1', 'key-2'],
|
||||
availableApiFormats: ['gemini:chat'],
|
||||
})
|
||||
|
||||
app.mount(root)
|
||||
await nextTick()
|
||||
|
||||
const applyLabel = [...document.body.querySelectorAll('label')]
|
||||
.find(label => label.textContent?.includes('应用自动获取设置'))
|
||||
const applyCheckbox = applyLabel?.querySelector<HTMLInputElement>('input[type="checkbox"]')
|
||||
expect(applyCheckbox).toBeTruthy()
|
||||
if (applyCheckbox) {
|
||||
applyCheckbox.checked = true
|
||||
applyCheckbox.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
}
|
||||
await nextTick()
|
||||
|
||||
document.body.querySelector<HTMLButtonElement>('[role="switch"]')?.click()
|
||||
await nextTick()
|
||||
|
||||
const text = document.body.textContent || ''
|
||||
expect(text).toContain('自动获取上游可用模型')
|
||||
expect(text).toContain('包含规则')
|
||||
expect(text).toContain('排除规则')
|
||||
expect(text).toContain('可用模型范围')
|
||||
expect(text).not.toContain('模型权限')
|
||||
expect(text).not.toContain('自动发现')
|
||||
|
||||
app.unmount()
|
||||
root.remove()
|
||||
})
|
||||
})
|
||||
@@ -54,7 +54,7 @@ describe('PoolManagementHeader', () => {
|
||||
|
||||
root.querySelector<HTMLButtonElement>('[title="添加账号"]')?.click()
|
||||
root.querySelector<HTMLButtonElement>('[title="点击调整号池调度"]')?.click()
|
||||
root.querySelector<HTMLButtonElement>('[title="密钥批量管理"]')?.click()
|
||||
root.querySelector<HTMLButtonElement>('[title="账号批量操作"]')?.click()
|
||||
root.querySelector<HTMLButtonElement>('[title="查看自适应热池指标"]')?.click()
|
||||
root.querySelector<HTMLButtonElement>('[title="刷新"]')?.click()
|
||||
|
||||
|
||||
@@ -23,12 +23,14 @@ function state(overrides: Partial<PoolKeyBatchEditState> = {}): PoolKeyBatchEdit
|
||||
maxProbeIntervalMinutes: '32',
|
||||
applyNote: false,
|
||||
note: '',
|
||||
applyModels: false,
|
||||
modelMode: '',
|
||||
unrestrictedModels: true,
|
||||
selectedModels: [],
|
||||
applyAutoFetchModels: false,
|
||||
autoFetchModels: false,
|
||||
includePatterns: '',
|
||||
excludePatterns: '',
|
||||
applyAllowedModels: false,
|
||||
unrestrictedModels: true,
|
||||
selectedModels: [],
|
||||
lockSelectedModels: true,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
@@ -49,40 +51,62 @@ describe('buildPoolKeyBatchUpdatePatch', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('builds a manual model policy and preserves explicit restrictions while disabling discovery', () => {
|
||||
it('builds an explicit model access range without changing automatic discovery', () => {
|
||||
const result = buildPoolKeyBatchUpdatePatch(state({
|
||||
applyModels: true,
|
||||
modelMode: 'manual',
|
||||
applyAllowedModels: true,
|
||||
unrestrictedModels: false,
|
||||
selectedModels: ['gpt-5.6-sol', 'gpt-5.6-sol', 'gpt-5.6-luna'],
|
||||
lockSelectedModels: false,
|
||||
}))
|
||||
|
||||
expect(result.patch).toEqual({
|
||||
auto_fetch_models: false,
|
||||
allowed_models: ['gpt-5.6-sol', 'gpt-5.6-luna'],
|
||||
locked_models: [],
|
||||
model_include_patterns: [],
|
||||
model_exclude_patterns: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('builds automatic discovery filters and locked models', () => {
|
||||
it('builds automatic discovery filters independently from the model access range', () => {
|
||||
const result = buildPoolKeyBatchUpdatePatch(state({
|
||||
applyModels: true,
|
||||
modelMode: 'automatic',
|
||||
selectedModels: ['gpt-5.6-sol'],
|
||||
applyAutoFetchModels: true,
|
||||
autoFetchModels: true,
|
||||
includePatterns: 'gpt-*,\nclaude-*',
|
||||
excludePatterns: '*-preview, *-beta',
|
||||
}))
|
||||
|
||||
expect(result.patch).toEqual({
|
||||
auto_fetch_models: true,
|
||||
locked_models: ['gpt-5.6-sol'],
|
||||
model_include_patterns: ['gpt-*', 'claude-*'],
|
||||
model_exclude_patterns: ['*-preview', '*-beta'],
|
||||
})
|
||||
})
|
||||
|
||||
it('disables automatic discovery without rewriting model filters or access limits', () => {
|
||||
const result = buildPoolKeyBatchUpdatePatch(state({
|
||||
applyAutoFetchModels: true,
|
||||
autoFetchModels: false,
|
||||
includePatterns: 'gpt-*',
|
||||
excludePatterns: '*-preview',
|
||||
}))
|
||||
|
||||
expect(result.patch).toEqual({
|
||||
auto_fetch_models: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('locks selected models only when the operator enables locking', () => {
|
||||
const result = buildPoolKeyBatchUpdatePatch(state({
|
||||
applyAllowedModels: true,
|
||||
unrestrictedModels: false,
|
||||
selectedModels: ['gpt-5.6-sol'],
|
||||
lockSelectedModels: true,
|
||||
}))
|
||||
|
||||
expect(result.patch).toEqual({
|
||||
allowed_models: ['gpt-5.6-sol'],
|
||||
locked_models: ['gpt-5.6-sol'],
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects empty fields and invalid ranges before the request is sent', () => {
|
||||
expect(buildPoolKeyBatchUpdatePatch(state()).error).toBe('请至少启用一个批量编辑字段')
|
||||
expect(buildPoolKeyBatchUpdatePatch(state({
|
||||
@@ -93,10 +117,9 @@ describe('buildPoolKeyBatchUpdatePatch', () => {
|
||||
cacheTtlMinutes: '61',
|
||||
})).error).toBe('缓存 TTL 必须是 0-60 的整数')
|
||||
expect(buildPoolKeyBatchUpdatePatch(state({
|
||||
applyModels: true,
|
||||
modelMode: 'manual',
|
||||
applyAllowedModels: true,
|
||||
unrestrictedModels: false,
|
||||
})).error).toBe('请至少选择一个允许的模型')
|
||||
})).error).toBe('请至少选择一个可用模型')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import type { PoolKeyBatchUpdatePatch } from '@/api/endpoints/pool'
|
||||
|
||||
export type PoolKeyBatchModelMode = '' | 'manual' | 'automatic'
|
||||
|
||||
export interface PoolKeyBatchEditState {
|
||||
applyApiFormats: boolean
|
||||
apiFormats: string[]
|
||||
@@ -19,12 +17,14 @@ export interface PoolKeyBatchEditState {
|
||||
maxProbeIntervalMinutes: string
|
||||
applyNote: boolean
|
||||
note: string
|
||||
applyModels: boolean
|
||||
modelMode: PoolKeyBatchModelMode
|
||||
unrestrictedModels: boolean
|
||||
selectedModels: string[]
|
||||
applyAutoFetchModels: boolean
|
||||
autoFetchModels: boolean
|
||||
includePatterns: string
|
||||
excludePatterns: string
|
||||
applyAllowedModels: boolean
|
||||
unrestrictedModels: boolean
|
||||
selectedModels: string[]
|
||||
lockSelectedModels: boolean
|
||||
}
|
||||
|
||||
export interface PoolKeyBatchPatchBuildResult {
|
||||
@@ -120,27 +120,25 @@ export function buildPoolKeyBatchUpdatePatch(
|
||||
fieldLabels.push('备注')
|
||||
}
|
||||
|
||||
if (state.applyModels) {
|
||||
if (!state.modelMode) {
|
||||
return { patch: null, fieldLabels, error: '请选择模型权限管理方式' }
|
||||
}
|
||||
const selectedModels = uniqueTrimmed(state.selectedModels)
|
||||
if (state.modelMode === 'manual') {
|
||||
if (!state.unrestrictedModels && selectedModels.length === 0) {
|
||||
return { patch: null, fieldLabels, error: '请至少选择一个允许的模型' }
|
||||
}
|
||||
patch.auto_fetch_models = false
|
||||
patch.allowed_models = state.unrestrictedModels ? null : selectedModels
|
||||
patch.locked_models = []
|
||||
patch.model_include_patterns = []
|
||||
patch.model_exclude_patterns = []
|
||||
} else {
|
||||
patch.auto_fetch_models = true
|
||||
patch.locked_models = selectedModels
|
||||
if (state.applyAutoFetchModels) {
|
||||
patch.auto_fetch_models = state.autoFetchModels
|
||||
if (state.autoFetchModels) {
|
||||
patch.model_include_patterns = parsePoolKeyModelPatterns(state.includePatterns)
|
||||
patch.model_exclude_patterns = parsePoolKeyModelPatterns(state.excludePatterns)
|
||||
}
|
||||
fieldLabels.push('模型权限')
|
||||
fieldLabels.push('自动获取上游可用模型')
|
||||
}
|
||||
|
||||
if (state.applyAllowedModels) {
|
||||
const selectedModels = uniqueTrimmed(state.selectedModels)
|
||||
if (!state.unrestrictedModels && selectedModels.length === 0) {
|
||||
return { patch: null, fieldLabels, error: '请至少选择一个可用模型' }
|
||||
}
|
||||
patch.allowed_models = state.unrestrictedModels ? null : selectedModels
|
||||
patch.locked_models = state.unrestrictedModels || !state.lockSelectedModels
|
||||
? []
|
||||
: selectedModels
|
||||
fieldLabels.push('可用模型范围')
|
||||
}
|
||||
|
||||
if (fieldLabels.length === 0) {
|
||||
|
||||
@@ -1207,7 +1207,6 @@ const legacyExactEnglishMessages: Record<string, string> = {
|
||||
'模型管理': 'Model management',
|
||||
'提供商管理': 'Provider management',
|
||||
'号池管理': 'Pool management',
|
||||
'密钥批量管理': 'Bulk key management',
|
||||
'模块管理': 'Module management',
|
||||
'系统扩展': 'System extensions',
|
||||
'Aether 提供高可插入的模块化管理机制,帮助连接外部服务和授权系统。': 'Aether provides a highly pluggable module management system for connecting external services and authorization systems.',
|
||||
|
||||
Reference in New Issue
Block a user