diff --git a/frontend/src/components/common/ModelMultiSelect.vue b/frontend/src/components/common/ModelMultiSelect.vue new file mode 100644 index 0000000..93e918b --- /dev/null +++ b/frontend/src/components/common/ModelMultiSelect.vue @@ -0,0 +1,117 @@ + + + 允许的模型 + + + + {{ modelValue.length ? `已选择 ${modelValue.length} 个` : '全部可用' }} + ({{ invalidModels.length }} 个已失效) + + + + + + + + + {{ modelName }} + (已失效) + + + + + {{ model.name }} + + + 暂无可用模型 + + + + + + + diff --git a/frontend/src/components/common/index.ts b/frontend/src/components/common/index.ts index a0c0ac2..7ba0d83 100644 --- a/frontend/src/components/common/index.ts +++ b/frontend/src/components/common/index.ts @@ -7,3 +7,6 @@ export { default as EmptyState } from './EmptyState.vue' export { default as AlertDialog } from './AlertDialog.vue' export { default as LoadingState } from './LoadingState.vue' + +// 表单组件 +export { default as ModelMultiSelect } from './ModelMultiSelect.vue' diff --git a/frontend/src/composables/useInvalidModels.ts b/frontend/src/composables/useInvalidModels.ts new file mode 100644 index 0000000..e5bd95d --- /dev/null +++ b/frontend/src/composables/useInvalidModels.ts @@ -0,0 +1,34 @@ +import { computed, type Ref, type ComputedRef } from 'vue' + +/** + * 检测失效模型的 composable + * + * 用于检测 allowed_models 中已不存在于 globalModels 的模型名称, + * 这些模型可能已被删除但引用未清理。 + * + * @example + * ```typescript + * const { invalidModels } = useInvalidModels( + * computed(() => form.value.allowed_models), + * globalModels + * ) + * ``` + */ +export interface ModelWithName { + name: string +} + +export function useInvalidModels( + allowedModels: Ref | ComputedRef, + globalModels: Ref +): { invalidModels: ComputedRef } { + const validModelNames = computed(() => + new Set(globalModels.value.map(m => m.name)) + ) + + const invalidModels = computed(() => + allowedModels.value.filter(name => !validModelNames.value.has(name)) + ) + + return { invalidModels } +} diff --git a/frontend/src/features/api-keys/components/StandaloneKeyFormDialog.vue b/frontend/src/features/api-keys/components/StandaloneKeyFormDialog.vue index 35b9b13..a92a06a 100644 --- a/frontend/src/features/api-keys/components/StandaloneKeyFormDialog.vue +++ b/frontend/src/features/api-keys/components/StandaloneKeyFormDialog.vue @@ -244,55 +244,10 @@ - - 允许的模型 - - - - {{ form.allowed_models.length ? `已选择 ${form.allowed_models.length} 个` : '全部可用' }} - - - - - - - - {{ model.name }} - - - 暂无可用模型 - - - - + @@ -327,6 +282,7 @@ import { } from '@/components/ui' import { Plus, SquarePen, Key, Shield, ChevronDown } from 'lucide-vue-next' import { useFormDialog } from '@/composables/useFormDialog' +import { ModelMultiSelect } from '@/components/common' import { getProvidersSummary } from '@/api/endpoints/providers' import { getGlobalModels } from '@/api/global-models' import { adminApi } from '@/api/admin' @@ -363,7 +319,6 @@ const saving = ref(false) // 下拉框状态 const providerDropdownOpen = ref(false) const apiFormatDropdownOpen = ref(false) -const modelDropdownOpen = ref(false) // 选项数据 const providers = ref([]) @@ -397,7 +352,6 @@ function resetForm() { } providerDropdownOpen.value = false apiFormatDropdownOpen.value = false - modelDropdownOpen.value = false } function loadKeyData() { diff --git a/frontend/src/features/users/components/UserFormDialog.vue b/frontend/src/features/users/components/UserFormDialog.vue index 65cd958..c7e7d42 100644 --- a/frontend/src/features/users/components/UserFormDialog.vue +++ b/frontend/src/features/users/components/UserFormDialog.vue @@ -316,55 +316,10 @@ - - 允许的模型 - - - - {{ form.allowed_models.length ? `已选择 ${form.allowed_models.length} 个` : '全部可用' }} - - - - - - - - {{ model.name }} - - - 暂无可用模型 - - - - + @@ -404,10 +359,12 @@ import { } from '@/components/ui' import { UserPlus, SquarePen, ChevronDown } from 'lucide-vue-next' import { useFormDialog } from '@/composables/useFormDialog' +import { ModelMultiSelect } from '@/components/common' import { getProvidersSummary } from '@/api/endpoints/providers' import { getGlobalModels } from '@/api/global-models' import { adminApi } from '@/api/admin' import { log } from '@/utils/logger' +import type { ProviderWithEndpointsSummary, GlobalModelResponse } from '@/api/endpoints/types' export interface UserFormData { id?: string @@ -440,11 +397,10 @@ const roleSelectOpen = ref(false) // 下拉框状态 const providerDropdownOpen = ref(false) const endpointDropdownOpen = ref(false) -const modelDropdownOpen = ref(false) // 选项数据 -const providers = ref([]) -const globalModels = ref([]) +const providers = ref([]) +const globalModels = ref([]) const apiFormats = ref>([]) // 表单数据 diff --git a/src/services/model/global_model.py b/src/services/model/global_model.py index 32609f3..7f222d3 100644 --- a/src/services/model/global_model.py +++ b/src/services/model/global_model.py @@ -148,6 +148,8 @@ class GlobalModelService: 删除 GlobalModel 默认行为: 级联删除所有关联的 Provider 模型实现 + 注意: 不清理 API Key 和 User 的 allowed_models 引用, + 保留无效引用可让用户在前端看到"已失效"的模型,便于手动清理或等待重建同名模型 """ global_model = GlobalModelService.get_global_model(db, global_model_id)