refactor(frontend): refactor model management with aliases, remove mappings UI

This commit is contained in:
fawney19
2025-12-15 14:30:42 +08:00
parent 743f23e640
commit 03ee6c16d9
14 changed files with 62 additions and 1369 deletions

View File

@@ -1,121 +0,0 @@
/**
* 模型别名管理 API
*/
import client from '../client'
import type { ModelMapping, ModelMappingCreate, ModelMappingUpdate } from './types'
export interface ModelAlias {
id: string
alias: string
global_model_id: string
global_model_name: string | null
global_model_display_name: string | null
provider_id: string | null
provider_name: string | null
scope: 'global' | 'provider'
mapping_type: 'alias' | 'mapping'
is_active: boolean
created_at: string
updated_at: string
}
export interface CreateModelAliasRequest {
alias: string
global_model_id: string
provider_id?: string | null
mapping_type?: 'alias' | 'mapping'
is_active?: boolean
}
export interface UpdateModelAliasRequest {
alias?: string
global_model_id?: string
provider_id?: string | null
mapping_type?: 'alias' | 'mapping'
is_active?: boolean
}
function transformMapping(mapping: ModelMapping): ModelAlias {
return {
id: mapping.id,
alias: mapping.source_model,
global_model_id: mapping.target_global_model_id,
global_model_name: mapping.target_global_model_name,
global_model_display_name: mapping.target_global_model_display_name,
provider_id: mapping.provider_id ?? null,
provider_name: mapping.provider_name ?? null,
scope: mapping.scope,
mapping_type: mapping.mapping_type || 'alias',
is_active: mapping.is_active,
created_at: mapping.created_at,
updated_at: mapping.updated_at
}
}
/**
* 获取别名列表
*/
export async function getAliases(params?: {
provider_id?: string
global_model_id?: string
is_active?: boolean
skip?: number
limit?: number
}): Promise<ModelAlias[]> {
const response = await client.get('/api/admin/models/mappings', {
params: {
provider_id: params?.provider_id,
target_global_model_id: params?.global_model_id,
is_active: params?.is_active,
skip: params?.skip,
limit: params?.limit
}
})
return (response.data as ModelMapping[]).map(transformMapping)
}
/**
* 获取单个别名
*/
export async function getAlias(id: string): Promise<ModelAlias> {
const response = await client.get(`/api/admin/models/mappings/${id}`)
return transformMapping(response.data)
}
/**
* 创建别名
*/
export async function createAlias(data: CreateModelAliasRequest): Promise<ModelAlias> {
const payload: ModelMappingCreate = {
source_model: data.alias,
target_global_model_id: data.global_model_id,
provider_id: data.provider_id ?? null,
mapping_type: data.mapping_type ?? 'alias',
is_active: data.is_active ?? true
}
const response = await client.post('/api/admin/models/mappings', payload)
return transformMapping(response.data)
}
/**
* 更新别名
*/
export async function updateAlias(id: string, data: UpdateModelAliasRequest): Promise<ModelAlias> {
const payload: ModelMappingUpdate = {
source_model: data.alias,
target_global_model_id: data.global_model_id,
provider_id: data.provider_id ?? null,
mapping_type: data.mapping_type,
is_active: data.is_active
}
const response = await client.patch(`/api/admin/models/mappings/${id}`, payload)
return transformMapping(response.data)
}
/**
* 删除别名
*/
export async function deleteAlias(id: string): Promise<void> {
await client.delete(`/api/admin/models/mappings/${id}`)
}

View File

@@ -4,6 +4,5 @@ export * from './endpoints'
export * from './keys'
export * from './health'
export * from './models'
export * from './aliases'
export * from './adaptive'
export * from './global-models'

View File

@@ -5,9 +5,6 @@ import type {
ModelUpdate,
ModelCatalogResponse,
ProviderAvailableSourceModelsResponse,
UpdateModelMappingRequest,
UpdateModelMappingResponse,
DeleteModelMappingResponse
} from './types'
/**
@@ -99,27 +96,6 @@ export async function getProviderAvailableSourceModels(
return response.data
}
/**
* 更新目录中的模型映射
*/
export async function updateCatalogMapping(
mappingId: string,
data: UpdateModelMappingRequest
): Promise<UpdateModelMappingResponse> {
const response = await client.put(`/api/admin/models/catalog/mappings/${mappingId}`, data)
return response.data
}
/**
* 删除目录中的模型映射
*/
export async function deleteCatalogMapping(
mappingId: string
): Promise<DeleteModelMappingResponse> {
const response = await client.delete(`/api/admin/models/catalog/mappings/${mappingId}`)
return response.data
}
/**
* 批量为 Provider 关联 GlobalModels
*/

View File

@@ -211,11 +211,17 @@ export interface ConcurrencyStatus {
key_max_concurrent?: number
}
export interface ProviderModelAlias {
name: string
priority: number // 优先级(数字越小优先级越高)
}
export interface Model {
id: string
provider_id: string
global_model_id?: string // 关联的 GlobalModel ID
provider_model_name: string // Provider 侧的模型名称(原 name
provider_model_name: string // Provider 侧的模型名称
provider_model_aliases?: ProviderModelAlias[] | null // 模型名称别名列表(带优先级)
// 原始配置值(可能为空,为空时使用 GlobalModel 默认值)
price_per_request?: number | null // 按次计费价格
tiered_pricing?: TieredPricingConfig | null // 阶梯计费配置
@@ -244,7 +250,8 @@ export interface Model {
}
export interface ModelCreate {
provider_model_name: string // Provider 侧的模型名称(原 name
provider_model_name: string // Provider 侧的模型名称
provider_model_aliases?: ProviderModelAlias[] // 模型名称别名列表(带优先级)
global_model_id: string // 关联的 GlobalModel ID必填
// 计费配置(可选,为空时使用 GlobalModel 默认值)
price_per_request?: number // 按次计费价格
@@ -261,6 +268,7 @@ export interface ModelCreate {
export interface ModelUpdate {
provider_model_name?: string
provider_model_aliases?: ProviderModelAlias[] | null // 模型名称别名列表(带优先级)
global_model_id?: string
price_per_request?: number | null // 按次计费价格null 表示清空/使用默认值)
tiered_pricing?: TieredPricingConfig | null // 阶梯计费配置
@@ -273,21 +281,6 @@ export interface ModelUpdate {
is_available?: boolean
}
export interface ModelMapping {
id: string
source_model: string // 别名/源模型名
target_global_model_id: string // 目标 GlobalModel ID
target_global_model_name: string | null
target_global_model_display_name: string | null
provider_id: string | null
provider_name: string | null
scope: 'global' | 'provider'
mapping_type: 'alias' | 'mapping'
is_active: boolean
created_at: string
updated_at: string
}
export interface ModelCapabilities {
supports_vision: boolean
supports_function_calling: boolean
@@ -335,7 +328,6 @@ export interface ModelCatalogItem {
global_model_name: string // GlobalModel.name原 source_model
display_name: string // GlobalModel.display_name
description?: string | null // GlobalModel.description
aliases: string[] // 所有指向该 GlobalModel 的别名列表
providers: ModelCatalogProviderDetail[] // 支持该模型的 Provider 列表
price_range: ModelPriceRange // 价格区间
total_providers: number
@@ -351,8 +343,6 @@ export interface ProviderAvailableSourceModel {
global_model_name: string // GlobalModel.name原 source_model
display_name: string // GlobalModel.display_name
provider_model_name: string // Model.provider_model_nameProvider 侧的模型名)
has_alias: boolean // 是否有别名指向该 GlobalModel
aliases: string[] // 别名列表
model_id?: string | null // Model.id
price: ProviderModelPriceInfo
capabilities: ModelCapabilities
@@ -371,65 +361,6 @@ export interface BatchAssignProviderConfig {
model_id?: string
}
export interface BatchAssignModelMappingRequest {
global_model_id: string // 要分配的 GlobalModel ID原 source_model
providers: BatchAssignProviderConfig[]
}
export interface BatchAssignProviderResult {
provider_id: string
mapping_id?: string | null
created_model: boolean
model_id?: string | null
updated: boolean
}
export interface BatchAssignError {
provider_id: string
error: string
}
export interface BatchAssignModelMappingResponse {
success: boolean
created_mappings: BatchAssignProviderResult[]
errors: BatchAssignError[]
}
export interface ModelMappingCreate {
source_model: string // 源模型名或别名
target_global_model_id: string // 目标 GlobalModel ID
provider_id?: string | null
mapping_type?: 'alias' | 'mapping'
is_active?: boolean
}
export interface ModelMappingUpdate {
source_model?: string // 源模型名或别名
target_global_model_id?: string // 目标 GlobalModel ID
provider_id?: string | null
mapping_type?: 'alias' | 'mapping'
is_active?: boolean
}
export interface UpdateModelMappingRequest {
source_model?: string
target_global_model_id?: string
provider_id?: string | null
mapping_type?: 'alias' | 'mapping'
is_active?: boolean
}
export interface UpdateModelMappingResponse {
success: boolean
mapping_id: string
message?: string
}
export interface DeleteModelMappingResponse {
success: boolean
message?: string
}
export interface AdaptiveStatsResponse {
adaptive_mode: boolean
current_limit: number | null

View File

@@ -1,384 +0,0 @@
<template>
<Dialog
:model-value="open"
:title="dialogTitle"
:description="dialogDescription"
:icon="dialogIcon"
size="md"
@update:model-value="handleDialogUpdate"
>
<form
class="space-y-4"
@submit.prevent="handleSubmit"
>
<!-- 模式选择仅创建时显示 -->
<div
v-if="!isEditMode"
class="space-y-2"
>
<Label>创建类型 *</Label>
<div class="grid grid-cols-2 gap-3">
<button
type="button"
class="p-3 rounded-lg border-2 text-left transition-all"
:class="[
form.mapping_type === 'alias'
? 'border-primary bg-primary/5'
: 'border-border hover:border-primary/50'
]"
@click="form.mapping_type = 'alias'"
>
<div class="font-medium text-sm">
别名
</div>
<div class="text-xs text-muted-foreground mt-1">
名称简写按目标模型计费
</div>
</button>
<button
type="button"
class="p-3 rounded-lg border-2 text-left transition-all"
:class="[
form.mapping_type === 'mapping'
? 'border-primary bg-primary/5'
: 'border-border hover:border-primary/50'
]"
@click="form.mapping_type = 'mapping'"
>
<div class="font-medium text-sm">
映射
</div>
<div class="text-xs text-muted-foreground mt-1">
模型降级按源模型计费
</div>
</button>
</div>
</div>
<!-- 模式说明 -->
<div class="rounded-lg border border-border bg-muted/50 p-3 text-sm">
<p class="text-foreground font-medium mb-1">
{{ form.mapping_type === 'alias' ? '别名模式' : '映射模式' }}
</p>
<p class="text-muted-foreground text-xs">
{{ form.mapping_type === 'alias'
? '用户请求此别名时,会路由到目标模型,并按目标模型价格计费。'
: '将源模型的请求转发到目标模型处理,按源模型价格计费。' }}
</p>
</div>
<!-- Provider 选择/作用范围 -->
<div
v-if="showProviderSelect"
class="space-y-2"
>
<Label>作用范围</Label>
<!-- 固定 Provider 时显示只读 -->
<div
v-if="fixedProvider"
class="px-3 py-2 border rounded-md bg-muted/50 text-sm"
>
{{ fixedProvider.display_name || fixedProvider.name }}
</div>
<!-- 否则显示可选择的下拉 -->
<Select
v-else
v-model:open="providerSelectOpen"
:model-value="form.provider_id || 'global'"
@update:model-value="handleProviderChange"
>
<SelectTrigger class="w-full">
<SelectValue placeholder="选择作用范围" />
</SelectTrigger>
<SelectContent>
<SelectItem value="global">
全局所有 Provider
</SelectItem>
<SelectItem
v-for="p in providers"
:key="p.id"
:value="p.id"
>
{{ p.display_name || p.name }}
</SelectItem>
</SelectContent>
</Select>
</div>
<!-- 别名模式别名名称 -->
<div
v-if="form.mapping_type === 'alias'"
class="space-y-2"
>
<Label for="alias-name">别名名称 *</Label>
<Input
id="alias-name"
v-model="form.alias"
placeholder="如sonnet, opus"
:disabled="isEditMode"
required
/>
<p class="text-xs text-muted-foreground">
{{ isEditMode ? '创建后不可修改' : '用户将使用此名称请求模型' }}
</p>
</div>
<!-- 映射模式选择源模型 -->
<div
v-else
class="space-y-2"
>
<Label>源模型 (用户请求的模型) *</Label>
<Select
v-model:open="sourceModelSelectOpen"
:model-value="form.alias"
:disabled="isEditMode"
@update:model-value="form.alias = $event"
>
<SelectTrigger
class="w-full"
:class="{ 'opacity-50': isEditMode }"
>
<SelectValue placeholder="请选择源模型" />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="model in availableSourceModels"
:key="model.id"
:value="model.name"
>
{{ model.display_name }} ({{ model.name }})
</SelectItem>
</SelectContent>
</Select>
<p class="text-xs text-muted-foreground">
{{ isEditMode ? '创建后不可修改' : '选择要被映射的源模型,计费将按此模型价格' }}
</p>
</div>
<!-- 目标模型选择 -->
<div class="space-y-2">
<Label>
{{ form.mapping_type === 'alias' ? '目标模型 *' : '目标模型 (实际处理请求) *' }}
</Label>
<!-- 固定目标模型时显示只读信息 -->
<div
v-if="fixedTargetModel"
class="px-3 py-2 border rounded-md bg-muted/50"
>
<span class="font-medium">{{ fixedTargetModel.display_name }}</span>
<span class="text-muted-foreground ml-1">({{ fixedTargetModel.name }})</span>
</div>
<!-- 否则显示下拉选择 -->
<Select
v-else
v-model:open="targetModelSelectOpen"
:model-value="form.global_model_id"
@update:model-value="form.global_model_id = $event"
>
<SelectTrigger class="w-full">
<SelectValue placeholder="请选择模型" />
</SelectTrigger>
<SelectContent>
<SelectItem
v-for="model in availableTargetModels"
:key="model.id"
:value="model.id"
>
{{ model.display_name }} ({{ model.name }})
</SelectItem>
</SelectContent>
</Select>
</div>
</form>
<template #footer>
<Button
type="button"
variant="outline"
@click="handleCancel"
>
取消
</Button>
<Button
:disabled="submitting"
@click="handleSubmit"
>
<Loader2
v-if="submitting"
class="w-4 h-4 mr-2 animate-spin"
/>
{{ isEditMode ? '保存' : '创建' }}
</Button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { Loader2, Tag, SquarePen } from 'lucide-vue-next'
import { Dialog, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@/components/ui'
import Button from '@/components/ui/button.vue'
import Input from '@/components/ui/input.vue'
import Label from '@/components/ui/label.vue'
import { useToast } from '@/composables/useToast'
import { useFormDialog } from '@/composables/useFormDialog'
import type { ModelAlias, CreateModelAliasRequest, UpdateModelAliasRequest } from '@/api/endpoints/aliases'
import type { GlobalModelResponse } from '@/api/global-models'
export interface ProviderOption {
id: string
name: string
display_name?: string
}
interface AliasFormData {
alias: string
global_model_id: string
provider_id: string | null
mapping_type: 'alias' | 'mapping'
is_active: boolean
}
const props = withDefaults(defineProps<{
open: boolean
editingAlias?: ModelAlias | null
globalModels: GlobalModelResponse[]
providers?: ProviderOption[]
fixedTargetModel?: GlobalModelResponse | null // 用于从模型详情抽屉打开时固定目标模型
fixedProvider?: ProviderOption | null // 用于 Provider 特定别名固定 Provider
showProviderSelect?: boolean // 是否显示 Provider 选择(默认 true
}>(), {
editingAlias: null,
providers: () => [],
fixedTargetModel: null,
fixedProvider: null,
showProviderSelect: true
})
const emit = defineEmits<{
'update:open': [value: boolean]
'submit': [data: CreateModelAliasRequest | UpdateModelAliasRequest, isEdit: boolean]
}>()
const { error: showError } = useToast()
// 状态
const submitting = ref(false)
const providerSelectOpen = ref(false)
const sourceModelSelectOpen = ref(false)
const targetModelSelectOpen = ref(false)
const form = ref<AliasFormData>({
alias: '',
global_model_id: '',
provider_id: null,
mapping_type: 'alias',
is_active: true,
})
// 处理 Provider 选择变化
function handleProviderChange(value: string) {
form.value.provider_id = value === 'global' ? null : value
}
// 重置表单
function resetForm() {
form.value = {
alias: '',
global_model_id: props.fixedTargetModel?.id || '',
provider_id: props.fixedProvider?.id || null,
mapping_type: 'alias',
is_active: true,
}
}
// 加载别名数据(编辑模式)
function loadAliasData() {
if (!props.editingAlias) return
form.value = {
alias: props.editingAlias.alias,
global_model_id: props.editingAlias.global_model_id,
provider_id: props.editingAlias.provider_id,
mapping_type: props.editingAlias.mapping_type || 'alias',
is_active: props.editingAlias.is_active,
}
}
// 使用 useFormDialog 统一处理对话框逻辑
const { isEditMode, handleDialogUpdate, handleCancel } = useFormDialog({
isOpen: () => props.open,
entity: () => props.editingAlias,
isLoading: submitting,
onClose: () => emit('update:open', false),
loadData: loadAliasData,
resetForm,
})
// 对话框标题
const dialogTitle = computed(() => {
if (isEditMode.value) {
return form.value.mapping_type === 'mapping' ? '编辑映射' : '编辑别名'
}
if (props.fixedProvider) {
return `创建 ${props.fixedProvider.display_name || props.fixedProvider.name} 特定别名/映射`
}
return '创建别名/映射'
})
// 对话框描述
const dialogDescription = computed(() => {
if (isEditMode.value) {
return form.value.mapping_type === 'mapping' ? '修改模型映射配置' : '修改别名设置'
}
return '为模型创建别名或映射规则'
})
// 对话框图标
const dialogIcon = computed(() => isEditMode.value ? SquarePen : Tag)
// 映射模式下可选的源模型(排除已选择的目标模型)
const availableSourceModels = computed(() => {
return props.globalModels.filter(m => m.id !== form.value.global_model_id)
})
// 可选的目标模型(映射模式下排除已选择的源模型)
const availableTargetModels = computed(() => {
if (form.value.mapping_type === 'mapping' && form.value.alias) {
// 找到源模型对应的 GlobalModel
const sourceModel = props.globalModels.find(m => m.name === form.value.alias)
if (sourceModel) {
return props.globalModels.filter(m => m.id !== sourceModel.id)
}
}
return props.globalModels
})
// 提交表单
async function handleSubmit() {
if (!form.value.alias) {
showError(form.value.mapping_type === 'alias' ? '请输入别名名称' : '请选择源模型', '错误')
return
}
const targetModelId = props.fixedTargetModel?.id || form.value.global_model_id
if (!targetModelId) {
showError('请选择目标模型', '错误')
return
}
submitting.value = true
try {
const data: CreateModelAliasRequest | UpdateModelAliasRequest = {
alias: form.value.alias,
global_model_id: targetModelId,
provider_id: props.fixedProvider?.id || form.value.provider_id,
mapping_type: form.value.mapping_type,
is_active: form.value.is_active,
}
emit('submit', data, !!props.editingAlias)
} finally {
submitting.value = false
}
}
</script>

View File

@@ -104,19 +104,6 @@
<span class="hidden sm:inline">关联提供商</span>
<span class="sm:hidden">提供商</span>
</button>
<button
type="button"
class="flex-1 px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium rounded-md transition-all duration-200"
:class="[
detailTab === 'aliases'
? 'bg-primary text-primary-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
]"
@click="detailTab = 'aliases'"
>
<span class="hidden sm:inline">别名/映射</span>
<span class="sm:hidden">别名</span>
</button>
</div>
<!-- Tab 内容 -->
@@ -684,236 +671,6 @@
</div>
</Card>
</div>
<!-- Tab 3: 别名 -->
<div v-show="detailTab === 'aliases'">
<Card class="overflow-hidden">
<!-- 标题栏 -->
<div class="px-4 py-3 border-b border-border/60">
<div class="flex items-center justify-between gap-4">
<div>
<h4 class="text-sm font-semibold">
别名与映射
</h4>
</div>
<div class="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
title="添加别名/映射"
@click="$emit('addAlias')"
>
<Plus class="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
title="刷新"
@click="$emit('refreshAliases')"
>
<RefreshCw
class="w-3.5 h-3.5"
:class="loadingAliases ? 'animate-spin' : ''"
/>
</Button>
</div>
</div>
</div>
<!-- 表格内容 -->
<div
v-if="loadingAliases"
class="flex items-center justify-center py-12"
>
<Loader2 class="w-6 h-6 animate-spin text-primary" />
</div>
<template v-else-if="aliases.length > 0">
<!-- 桌面端表格 -->
<Table class="hidden sm:table">
<TableHeader>
<TableRow class="border-b border-border/60 hover:bg-transparent">
<TableHead class="h-10 font-semibold">
别名
</TableHead>
<TableHead class="w-[80px] h-10 font-semibold">
类型
</TableHead>
<TableHead class="w-[100px] h-10 font-semibold">
作用域
</TableHead>
<TableHead class="w-[100px] h-10 font-semibold text-center">
操作
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow
v-for="alias in aliases"
:key="alias.id"
class="border-b border-border/40 hover:bg-muted/30 transition-colors"
>
<TableCell class="py-3">
<div class="flex items-center gap-2">
<span
class="w-2 h-2 rounded-full shrink-0"
:class="alias.is_active ? 'bg-green-500' : 'bg-gray-300'"
:title="alias.is_active ? '活跃' : '停用'"
/>
<code class="text-sm font-medium bg-muted px-1.5 py-0.5 rounded">{{ alias.alias }}</code>
</div>
</TableCell>
<TableCell class="py-3">
<Badge
variant="secondary"
class="text-xs"
>
{{ alias.mapping_type === 'mapping' ? '映射' : '别名' }}
</Badge>
</TableCell>
<TableCell class="py-3">
<Badge
v-if="alias.provider_id"
variant="outline"
class="text-xs truncate max-w-[90px]"
:title="alias.provider_name || 'Provider'"
>
{{ alias.provider_name || 'Provider' }}
</Badge>
<Badge
v-else
variant="default"
class="text-xs"
>
全局
</Badge>
</TableCell>
<TableCell class="py-3 text-center">
<div class="flex items-center justify-center gap-0.5">
<Button
variant="ghost"
size="icon"
class="h-7 w-7"
title="编辑"
@click="$emit('editAlias', alias)"
>
<Edit class="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
class="h-7 w-7"
:title="alias.is_active ? '停用' : '启用'"
@click="$emit('toggleAliasStatus', alias)"
>
<Power class="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
class="h-7 w-7"
title="删除"
@click="$emit('deleteAlias', alias)"
>
<Trash2 class="w-3.5 h-3.5" />
</Button>
</div>
</TableCell>
</TableRow>
</TableBody>
</Table>
<!-- 移动端卡片列表 -->
<div class="sm:hidden divide-y divide-border/40">
<div
v-for="alias in aliases"
:key="alias.id"
class="p-4 space-y-2"
>
<div class="flex items-start justify-between gap-3">
<div class="flex items-center gap-2 min-w-0 flex-1">
<span
class="w-2 h-2 rounded-full shrink-0"
:class="alias.is_active ? 'bg-green-500' : 'bg-gray-300'"
/>
<code class="text-sm font-medium bg-muted px-1.5 py-0.5 rounded truncate">{{ alias.alias }}</code>
</div>
<div class="flex items-center gap-1 shrink-0">
<Button
variant="ghost"
size="icon"
class="h-7 w-7"
@click="$emit('editAlias', alias)"
>
<Edit class="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
class="h-7 w-7"
@click="$emit('toggleAliasStatus', alias)"
>
<Power class="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
class="h-7 w-7"
@click="$emit('deleteAlias', alias)"
>
<Trash2 class="w-3.5 h-3.5" />
</Button>
</div>
</div>
<div class="flex items-center gap-2">
<Badge
variant="secondary"
class="text-xs"
>
{{ alias.mapping_type === 'mapping' ? '映射' : '别名' }}
</Badge>
<Badge
v-if="alias.provider_id"
variant="outline"
class="text-xs truncate max-w-[120px]"
>
{{ alias.provider_name || 'Provider' }}
</Badge>
<Badge
v-else
variant="default"
class="text-xs"
>
全局
</Badge>
</div>
</div>
</div>
</template>
<div
v-else
class="text-center py-12"
>
<!-- 空状态 -->
<Tag class="w-12 h-12 mx-auto text-muted-foreground/30 mb-3" />
<p class="text-sm text-muted-foreground">
暂无别名或映射
</p>
<Button
size="sm"
variant="outline"
class="mt-4"
@click="$emit('addAlias')"
>
<Plus class="w-4 h-4 mr-1" />
添加别名/映射
</Button>
</div>
</Card>
</div>
</div>
</Card>
</div>
@@ -931,7 +688,6 @@ import {
Zap,
Image,
Building2,
Tag,
Plus,
Edit,
Trash2,
@@ -955,13 +711,11 @@ import TableCell from '@/components/ui/table-cell.vue'
// 使用外部类型定义
import type { GlobalModelResponse } from '@/api/global-models'
import type { ModelAlias } from '@/api/endpoints/aliases'
import type { TieredPricingConfig, PricingTier } from '@/api/endpoints/types'
import type { CapabilityDefinition } from '@/api/endpoints'
const props = withDefaults(defineProps<Props>(), {
loadingProviders: false,
loadingAliases: false,
hasBlockingDialogOpen: false,
})
const emit = defineEmits<{
@@ -973,11 +727,6 @@ const emit = defineEmits<{
'deleteProvider': [provider: any]
'toggleProviderStatus': [provider: any]
'refreshProviders': []
'addAlias': []
'editAlias': [alias: ModelAlias]
'toggleAliasStatus': [alias: ModelAlias]
'deleteAlias': [alias: ModelAlias]
'refreshAliases': []
}>()
const { success: showSuccess, error: showError } = useToast()
@@ -985,9 +734,7 @@ interface Props {
model: GlobalModelResponse | null
open: boolean
providers: any[]
aliases: ModelAlias[]
loadingProviders?: boolean
loadingAliases?: boolean
hasBlockingDialogOpen?: boolean
capabilities?: CapabilityDefinition[]
}

View File

@@ -1,4 +1,3 @@
export { default as GlobalModelFormDialog } from './GlobalModelFormDialog.vue'
export { default as AliasDialog } from './AliasDialog.vue'
export { default as ModelDetailDrawer } from './ModelDetailDrawer.vue'
export { default as TieredPricingEditor } from './TieredPricingEditor.vue'

View File

@@ -526,14 +526,7 @@
@edit-model="handleEditModel"
@delete-model="handleDeleteModel"
@batch-assign="handleBatchAssign"
/>
<!-- 模型映射 -->
<MappingsTab
v-if="provider"
:key="`mappings-${provider.id}`"
:provider="provider"
@refresh="handleRelatedDataRefresh"
@manage-alias="handleManageAlias"
/>
</div>
</template>
@@ -636,6 +629,16 @@
@update:open="batchAssignDialogOpen = $event"
@changed="handleBatchAssignChanged"
/>
<!-- 模型别名管理对话框 -->
<ModelAliasDialog
v-if="open && provider"
:open="aliasDialogOpen"
:provider-id="provider.id"
:model="aliasEditingModel"
@update:open="aliasDialogOpen = $event"
@saved="handleAliasSaved"
/>
</template>
<script setup lang="ts">
@@ -663,9 +666,9 @@ import { getProvider, getProviderEndpoints } from '@/api/endpoints'
import {
KeyFormDialog,
KeyAllowedModelsDialog,
MappingsTab,
ModelsTab,
BatchAssignModelsDialog
BatchAssignModelsDialog,
ModelAliasDialog
} from '@/features/providers/components'
import EndpointFormDialog from '@/features/providers/components/EndpointFormDialog.vue'
import ProviderModelFormDialog from '@/features/providers/components/ProviderModelFormDialog.vue'
@@ -734,6 +737,10 @@ const deleteModelConfirmOpen = ref(false)
const modelToDelete = ref<Model | null>(null)
const batchAssignDialogOpen = ref(false)
// 别名管理相关状态
const aliasDialogOpen = ref(false)
const aliasEditingModel = ref<Model | null>(null)
// 拖动排序相关状态
const dragState = ref({
isDragging: false,
@@ -755,7 +762,8 @@ const hasBlockingDialogOpen = computed(() =>
deleteKeyConfirmOpen.value ||
modelFormDialogOpen.value ||
deleteModelConfirmOpen.value ||
batchAssignDialogOpen.value
batchAssignDialogOpen.value ||
aliasDialogOpen.value
)
// 监听 providerId 变化
@@ -784,6 +792,7 @@ watch(() => props.open, (newOpen) => {
keyAllowedModelsDialogOpen.value = false
deleteKeyConfirmOpen.value = false
batchAssignDialogOpen.value = false
aliasDialogOpen.value = false
// 重置临时数据
endpointToEdit.value = null
@@ -1021,6 +1030,19 @@ async function handleBatchAssignChanged() {
emit('refresh')
}
// 处理管理别名 - 打开别名对话框
function handleManageAlias(model: Model) {
aliasEditingModel.value = model
aliasDialogOpen.value = true
}
// 处理别名保存完成
async function handleAliasSaved() {
aliasEditingModel.value = null
await loadProvider()
emit('refresh')
}
// 处理模型保存完成
async function handleModelSaved() {
editingModel.value = null

View File

@@ -7,6 +7,6 @@ export { default as ProviderModelFormDialog } from './ProviderModelFormDialog.vu
export { default as ProviderDetailDrawer } from './ProviderDetailDrawer.vue'
export { default as EndpointHealthTimeline } from './EndpointHealthTimeline.vue'
export { default as BatchAssignModelsDialog } from './BatchAssignModelsDialog.vue'
export { default as ModelAliasDialog } from './ModelAliasDialog.vue'
export { default as MappingsTab } from './provider-tabs/MappingsTab.vue'
export { default as ModelsTab } from './provider-tabs/ModelsTab.vue'

View File

@@ -1,310 +0,0 @@
<template>
<Card class="overflow-hidden">
<!-- 标题头部 -->
<div class="p-4 border-b border-border/60">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<h3 class="text-sm font-semibold leading-none">
别名与映射管理
</h3>
</div>
<Button
v-if="!hideAddButton"
variant="outline"
size="sm"
class="h-8"
@click="openCreateDialog"
>
<Plus class="w-3.5 h-3.5 mr-1.5" />
创建别名/映射
</Button>
</div>
</div>
<!-- 加载状态 -->
<div
v-if="loading"
class="flex items-center justify-center py-12"
>
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
</div>
<!-- 别名列表 -->
<div
v-else-if="mappings.length > 0"
class="overflow-x-auto"
>
<table class="w-full text-sm">
<thead class="bg-muted/50 text-xs uppercase tracking-wide text-muted-foreground">
<tr>
<th class="text-left px-4 py-3 font-semibold">
名称
</th>
<th class="text-left px-4 py-3 font-semibold w-24">
类型
</th>
<th class="text-left px-4 py-3 font-semibold">
指向模型
</th>
<th
v-if="!hideAddButton"
class="px-4 py-3 font-semibold w-28 text-center"
>
操作
</th>
</tr>
</thead>
<tbody>
<tr
v-for="mapping in mappings"
:key="mapping.id"
class="border-b border-border/40 last:border-b-0 hover:bg-muted/30 transition-colors"
>
<td class="px-4 py-3">
<div class="flex items-center gap-2">
<!-- 状态指示灯 -->
<span
class="w-2 h-2 rounded-full shrink-0"
:class="mapping.is_active ? 'bg-green-500' : 'bg-gray-300'"
:title="mapping.is_active ? '活跃' : '停用'"
/>
<span class="font-mono">{{ mapping.alias }}</span>
</div>
</td>
<td class="px-4 py-3">
<Badge
variant="secondary"
class="text-xs"
>
{{ mapping.mapping_type === 'mapping' ? '映射' : '别名' }}
</Badge>
</td>
<td class="px-4 py-3">
{{ mapping.global_model_display_name || mapping.global_model_name }}
</td>
<td
v-if="!hideAddButton"
class="px-4 py-3"
>
<div class="flex justify-center gap-1.5">
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
title="编辑"
@click="openEditDialog(mapping)"
>
<Edit class="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
:disabled="togglingId === mapping.id"
:title="mapping.is_active ? '点击停用' : '点击启用'"
@click="toggleActive(mapping)"
>
<Power class="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
class="h-8 w-8 text-destructive hover:text-destructive"
title="删除"
@click="confirmDelete(mapping)"
>
<Trash2 class="w-3.5 h-3.5" />
</Button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 空状态 -->
<div
v-else
class="p-8 text-center text-muted-foreground"
>
<ArrowLeftRight class="w-12 h-12 mx-auto mb-3 opacity-50" />
<p class="text-sm">
暂无特定别名/映射
</p>
<p class="text-xs mt-1">
点击上方按钮添加
</p>
</div>
</Card>
<!-- 使用共享的 AliasDialog 组件 -->
<AliasDialog
:open="dialogOpen"
:editing-alias="editingAlias"
:global-models="availableModels"
:fixed-provider="fixedProviderOption"
:show-provider-select="true"
@update:open="handleDialogVisibility"
@submit="handleAliasSubmit"
/>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ArrowLeftRight, Plus, Edit, Trash2, Power } from 'lucide-vue-next'
import Card from '@/components/ui/card.vue'
import Badge from '@/components/ui/badge.vue'
import Button from '@/components/ui/button.vue'
import AliasDialog from '@/features/models/components/AliasDialog.vue'
import { useToast } from '@/composables/useToast'
import {
getAliases,
createAlias,
updateAlias,
deleteAlias,
type ModelAlias,
type CreateModelAliasRequest,
type UpdateModelAliasRequest,
} from '@/api/endpoints/aliases'
import { listGlobalModels, type GlobalModelResponse } from '@/api/global-models'
const props = withDefaults(defineProps<{
provider: any
hideAddButton?: boolean
}>(), {
hideAddButton: false
})
const emit = defineEmits<{
refresh: []
}>()
const { success, error: showError } = useToast()
// 状态
const loading = ref(false)
const submitting = ref(false)
const togglingId = ref<string | null>(null)
const mappings = ref<ModelAlias[]>([])
const availableModels = ref<GlobalModelResponse[]>([])
const dialogOpen = ref(false)
const editingAlias = ref<ModelAlias | null>(null)
// 固定的 Provider 选项(传递给 AliasDialog
const fixedProviderOption = computed(() => ({
id: props.provider.id,
name: props.provider.name,
display_name: props.provider.display_name
}))
// 加载映射 (实际返回的是该 Provider 的别名列表)
async function loadMappings() {
try {
loading.value = true
mappings.value = await getAliases({ provider_id: props.provider.id })
} catch (err: any) {
showError(err.response?.data?.detail || '加载失败', '错误')
} finally {
loading.value = false
}
}
// 加载可用的 GlobalModel 列表
async function loadAvailableModels() {
try {
const response = await listGlobalModels({ limit: 1000, is_active: true })
availableModels.value = response.models || []
} catch (err: any) {
showError(err.response?.data?.detail || '加载模型列表失败', '错误')
}
}
// 打开创建对话框
function openCreateDialog() {
editingAlias.value = null
dialogOpen.value = true
}
// 打开编辑对话框
function openEditDialog(alias: ModelAlias) {
editingAlias.value = alias
dialogOpen.value = true
}
// 处理对话框可见性变化
function handleDialogVisibility(value: boolean) {
dialogOpen.value = value
if (!value) {
editingAlias.value = null
}
}
// 处理别名提交(来自 AliasDialog 组件)
async function handleAliasSubmit(data: CreateModelAliasRequest | UpdateModelAliasRequest, isEdit: boolean) {
submitting.value = true
try {
if (isEdit && editingAlias.value) {
// 更新
await updateAlias(editingAlias.value.id, data as UpdateModelAliasRequest)
success(data.mapping_type === 'mapping' ? '映射已更新' : '别名已更新')
} else {
// 创建 - 确保 provider_id 设置为当前 Provider
const createData = data as CreateModelAliasRequest
createData.provider_id = props.provider.id
await createAlias(createData)
success(data.mapping_type === 'mapping' ? '映射已创建' : '别名已创建')
}
dialogOpen.value = false
editingAlias.value = null
await loadMappings()
emit('refresh')
} catch (err: any) {
const detail = err.response?.data?.detail || err.message
let errorMessage = detail
if (detail === '映射已存在') {
errorMessage = '该名称已存在,请使用其他名称'
}
showError(errorMessage, isEdit ? '更新失败' : '创建失败')
} finally {
submitting.value = false
}
}
// 切换启用状态
async function toggleActive(alias: ModelAlias) {
if (togglingId.value) return
togglingId.value = alias.id
try {
const newStatus = !alias.is_active
await updateAlias(alias.id, { is_active: newStatus })
alias.is_active = newStatus
} catch (err: any) {
showError(err.response?.data?.detail || '操作失败', '错误')
} finally {
togglingId.value = null
}
}
// 确认删除
async function confirmDelete(alias: ModelAlias) {
const typeName = alias.mapping_type === 'mapping' ? '映射' : '别名'
if (!confirm(`确定要删除${typeName} "${alias.alias}" 吗?`)) {
return
}
try {
await deleteAlias(alias.id)
success(`${typeName}已删除`)
await loadMappings()
emit('refresh')
} catch (err: any) {
showError(err.response?.data?.detail || err.message, '删除失败')
}
}
onMounted(() => {
loadMappings()
loadAvailableModels()
})
</script>

View File

@@ -165,6 +165,15 @@
>
<Edit class="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
title="管理别名"
@click="openAliasDialog(model)"
>
<Tag class="w-3.5 h-3.5" />
</Button>
<Button
variant="ghost"
size="icon"
@@ -209,7 +218,7 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { Box, Edit, Trash2, Layers, Eye, Wrench, Zap, Brain, Power, Copy, Image } from 'lucide-vue-next'
import { Box, Edit, Trash2, Layers, Eye, Wrench, Zap, Brain, Power, Copy, Image, Tag } from 'lucide-vue-next'
import Card from '@/components/ui/card.vue'
import Button from '@/components/ui/button.vue'
import { useToast } from '@/composables/useToast'
@@ -224,6 +233,7 @@ const emit = defineEmits<{
'editModel': [model: Model]
'deleteModel': [model: Model]
'batchAssign': []
'manageAlias': [model: Model]
}>()
const { error: showError, success: showSuccess } = useToast()
@@ -363,6 +373,11 @@ function openBatchAssignDialog() {
emit('batchAssign')
}
// 打开别名管理对话框
function openAliasDialog(model: Model) {
emit('manageAlias', model)
}
// 切换模型启用状态
async function toggleModelActive(model: Model) {
if (togglingModelId.value) return

View File

@@ -313,7 +313,6 @@ import {
Gauge,
Layers,
FolderTree,
Tag,
Box,
LogOut,
SunMoon,
@@ -411,7 +410,6 @@ const navigation = computed(() => {
{ name: '用户管理', href: '/admin/users', icon: Users },
{ name: '提供商', href: '/admin/providers', icon: FolderTree },
{ name: '模型管理', href: '/admin/models', icon: Layers },
{ name: '别名映射', href: '/admin/aliases', icon: Tag },
{ name: '独立密钥', href: '/admin/keys', icon: Key },
{ name: '使用记录', href: '/admin/usage', icon: BarChart3 },
]

View File

@@ -91,11 +91,6 @@ const routes: RouteRecordRaw[] = [
name: 'ModelManagement',
component: () => importWithRetry(() => import('@/views/admin/ModelManagement.vue'))
},
{
path: 'aliases',
name: 'AliasManagement',
component: () => importWithRetry(() => import('@/views/admin/AliasManagement.vue'))
},
{
path: 'health-monitor',
name: 'HealthMonitor',

View File

@@ -425,25 +425,12 @@
@success="handleModelFormSuccess"
/>
<!-- 创建/编辑别名/映射对话框 -->
<AliasDialog
:open="createAliasDialogOpen"
:editing-alias="editingAlias"
:global-models="globalModels"
:providers="providers"
:fixed-target-model="isTargetModelFixed ? selectedModel : null"
@update:open="handleAliasDialogUpdate"
@submit="handleAliasSubmit"
/>
<!-- 模型详情抽屉 -->
<ModelDetailDrawer
:model="selectedModel"
:open="!!selectedModel"
:providers="selectedModelProviders"
:aliases="selectedModelAliases"
:loading-providers="loadingModelProviders"
:loading-aliases="loadingModelAliases"
:has-blocking-dialog-open="hasBlockingDialogOpen"
:capabilities="capabilities"
@update:open="handleDrawerOpenChange"
@@ -454,11 +441,6 @@
@delete-provider="confirmDeleteProviderImplementation"
@toggle-provider-status="toggleProviderStatus"
@refresh-providers="refreshSelectedModelProviders"
@add-alias="openAddAliasDialog"
@edit-alias="openEditAliasDialog"
@toggle-alias-status="toggleAliasStatusFromDrawer"
@delete-alias="confirmDeleteAliasFromDrawer"
@refresh-aliases="refreshSelectedModelAliases"
/>
<!-- 批量添加关联提供商对话框 -->
@@ -736,9 +718,7 @@ import {
} from 'lucide-vue-next'
import ModelDetailDrawer from '@/features/models/components/ModelDetailDrawer.vue'
import GlobalModelFormDialog from '@/features/models/components/GlobalModelFormDialog.vue'
import AliasDialog from '@/features/models/components/AliasDialog.vue'
import ProviderModelFormDialog from '@/features/providers/components/ProviderModelFormDialog.vue'
import type { CreateModelAliasRequest, UpdateModelAliasRequest } from '@/api/endpoints/aliases'
import type { Model } from '@/api/endpoints'
import { useToast } from '@/composables/useToast'
import { useConfirm } from '@/composables/useConfirm'
@@ -768,13 +748,6 @@ import {
type GlobalModelResponse,
} from '@/api/global-models'
import { log } from '@/utils/logger'
import {
getAliases,
createAlias,
updateAlias,
deleteAlias,
type ModelAlias,
} from '@/api/endpoints/aliases'
import { getProvidersSummary } from '@/api/endpoints/providers'
import { getAllCapabilities, type CapabilityDefinition } from '@/api/endpoints'
@@ -788,13 +761,9 @@ const searchQuery = ref('')
const selectedModel = ref<GlobalModelResponse | null>(null)
const createModelDialogOpen = ref(false)
const editingModel = ref<GlobalModelResponse | null>(null)
const createAliasDialogOpen = ref(false)
const editingAliasId = ref<string | null>(null)
const isTargetModelFixed = ref(false) // 目标模型是否固定(从模型详情抽屉打开时为 true
// 数据
const globalModels = ref<GlobalModelResponse[]>([])
const allAliases = ref<ModelAlias[]>([])
const providers = ref<any[]>([])
const capabilities = ref<CapabilityDefinition[]>([])
@@ -804,9 +773,7 @@ const catalogPageSize = ref(20)
// 选中模型的详细数据
const selectedModelProviders = ref<any[]>([])
const selectedModelAliases = ref<ModelAlias[]>([])
const loadingModelProviders = ref(false)
const loadingModelAliases = ref(false)
// 批量添加关联提供商
const batchAddProvidersDialogOpen = ref(false)
@@ -876,19 +843,10 @@ function hasTieredPricing(model: GlobalModelResponse): boolean {
// 检测是否有对话框打开(防止误关闭抽屉)
const hasBlockingDialogOpen = computed(() =>
createModelDialogOpen.value ||
createAliasDialogOpen.value ||
batchAddProvidersDialogOpen.value ||
editProviderDialogOpen.value
)
// 编辑中的别名对象(用于传递给 AliasDialog
const editingAlias = computed(() => {
if (!editingAliasId.value) return null
return allAliases.value.find(a => a.id === editingAliasId.value) ||
selectedModelAliases.value.find(a => a.id === editingAliasId.value) ||
null
})
// 能力筛选
const capabilityFilters = ref({
streaming: false,
@@ -1131,11 +1089,8 @@ async function selectModel(model: GlobalModelResponse) {
selectedModel.value = model
detailTab.value = 'basic'
// 加载该模型的关联提供商和别名
await Promise.all([
loadModelProviders(model.id),
loadModelAliases(model.id)
])
// 加载该模型的关联提供商
await loadModelProviders(model.id)
}
// 加载指定模型的关联提供商
@@ -1187,27 +1142,6 @@ async function loadModelProviders(_globalModelId: string) {
}
}
// 加载指定模型的别名
async function loadModelAliases(globalModelId: string) {
loadingModelAliases.value = true
try {
const aliases = await getAliases({ limit: 1000 })
selectedModelAliases.value = aliases.filter(a => a.global_model_id === globalModelId)
} catch (err: any) {
log.error('加载别名失败:', err)
selectedModelAliases.value = []
} finally {
loadingModelAliases.value = false
}
}
// 刷新当前选中模型的别名
async function refreshSelectedModelAliases() {
if (selectedModel.value) {
await loadModelAliases(selectedModel.value.id)
}
}
// 刷新当前选中模型的关联提供商
async function refreshSelectedModelProviders() {
if (selectedModel.value) {
@@ -1329,14 +1263,6 @@ async function confirmDeleteProviderImplementation(provider: any) {
}
}
// 打开添加别名对话框(从模型详情抽屉)
function openAddAliasDialog() {
if (!selectedModel.value) return
editingAliasId.value = null
isTargetModelFixed.value = true // 目标模型固定为当前选中模型
createAliasDialogOpen.value = true
}
function openCreateModelDialog() {
editingModel.value = null
createModelDialogOpen.value = true
@@ -1391,106 +1317,6 @@ async function toggleModelStatus(model: GlobalModelResponse) {
}
}
async function loadAliases() {
try {
allAliases.value = await getAliases({ limit: 1000 })
} catch (err: any) {
showError(err.response?.data?.detail || err.message, '加载别名失败')
}
}
function openEditAliasDialog(alias: ModelAlias) {
editingAliasId.value = alias.id
isTargetModelFixed.value = false
createAliasDialogOpen.value = true
}
// 处理别名对话框关闭事件
function handleAliasDialogUpdate(value: boolean) {
createAliasDialogOpen.value = value
if (!value) {
editingAliasId.value = null
isTargetModelFixed.value = false
}
}
// 处理别名提交(来自 AliasDialog 组件)
async function handleAliasSubmit(data: CreateModelAliasRequest | UpdateModelAliasRequest, isEdit: boolean) {
submitting.value = true
try {
if (isEdit && editingAliasId.value) {
// 更新
await updateAlias(editingAliasId.value, data as UpdateModelAliasRequest)
success(data.mapping_type === 'mapping' ? '映射已更新' : '别名已更新')
} else {
// 创建
await createAlias(data as CreateModelAliasRequest)
success(data.mapping_type === 'mapping' ? '映射已创建' : '别名已创建')
}
createAliasDialogOpen.value = false
editingAliasId.value = null
isTargetModelFixed.value = false
// 刷新数据
await loadAliases()
if (selectedModel.value) {
await loadModelAliases(selectedModel.value.id)
}
// 刷新外层模型列表以更新 alias_count
await loadGlobalModels()
} catch (err: any) {
const detail = err.response?.data?.detail || err.message
// 优化错误提示文案
let errorMessage = detail
if (detail === '映射已存在') {
errorMessage = '目标作用域已存在同名别名,请先删除冲突的映射或选择其他作用域'
}
showError(errorMessage, isEdit ? '更新失败' : '创建失败')
} finally {
submitting.value = false
}
}
async function confirmDeleteAlias(alias: ModelAlias) {
const confirmed = await confirmDanger(
`确定要删除别名 "${alias.alias}" 吗?`,
'删除别名'
)
if (!confirmed) return
try {
await deleteAlias(alias.id)
success('别名已删除')
await loadAliases()
// 刷新外层模型列表以更新 alias_count
await loadGlobalModels()
} catch (err: any) {
showError(err.response?.data?.detail || err.message, '删除失败')
}
}
async function toggleAliasStatus(alias: ModelAlias) {
try {
await updateAlias(alias.id, { is_active: !alias.is_active })
alias.is_active = !alias.is_active
success(alias.is_active ? '别名已启用' : '别名已停用')
} catch (err: any) {
showError(err.response?.data?.detail || err.message, '操作失败')
}
}
// 从抽屉中切换别名状态
async function toggleAliasStatusFromDrawer(alias: ModelAlias) {
await toggleAliasStatus(alias)
await refreshSelectedModelAliases()
}
// 从抽屉中删除别名
async function confirmDeleteAliasFromDrawer(alias: ModelAlias) {
await confirmDeleteAlias(alias)
await refreshSelectedModelAliases()
}
async function refreshData() {
await loadGlobalModels()
}