@@ -416,20 +416,6 @@
@ endpoint -updated = " handleEndpointChanged "
/ >
<!-- 删除端点确认对话框 -- >
< AlertDialog
v-if = "open"
:model-value = "deleteEndpointConfirmOpen"
title = "删除端点"
: description = "`确定要删除端点 ${endpointToDelete?.api_format} 吗?这将同时删除其所有密钥。`"
confirm -text = " 删除 "
cancel -text = " 取消 "
type = "danger"
@ update : model -value = " deleteEndpointConfirmOpen = $ event "
@confirm ="confirmDeleteEndpoint"
@cancel ="deleteEndpointConfirmOpen = false"
/ >
<!-- 密钥编辑对话框 -- >
< KeyFormDialog
v-if = "open"
@@ -535,11 +521,9 @@ import EndpointFormDialog from '@/features/providers/components/EndpointFormDial
import ProviderModelFormDialog from '@/features/providers/components/ProviderModelFormDialog.vue'
import AlertDialog from '@/components/common/AlertDialog.vue'
import {
deleteEndpoint as deleteEndpointAPI ,
deleteEndpointKey ,
recoverKeyHealth ,
getProviderKeys ,
updateEndpoint ,
updateProviderKey ,
revealEndpointKey ,
type ProviderEndpoint ,
@@ -579,12 +563,9 @@ const loading = ref(false)
const provider = ref < any > ( null )
const endpoints = ref < ProviderEndpointWithKeys [ ] > ( [ ] )
const providerKeys = ref < EndpointAPIKey [ ] > ( [ ] ) // Provider 级别的 keys
const expandedEndpoints = ref < Set < string > > ( new Set ( ) )
// 端点相关状态
const endpointDialogOpen = ref ( false )
const deleteEndpointConfirmOpen = ref ( false )
const endpointToDelete = ref < ProviderEndpoint | null > ( null )
// 密钥相关状态
const keyFormDialogOpen = ref ( false )
@@ -593,13 +574,10 @@ const currentEndpoint = ref<ProviderEndpoint | null>(null)
const editingKey = ref < EndpointAPIKey | null > ( null )
const deleteKeyConfirmOpen = ref ( false )
const keyToDelete = ref < EndpointAPIKey | null > ( null )
const recoveringEndpointId = ref < string | null > ( null )
const togglingEndpointId = ref < string | null > ( null )
const togglingKeyId = ref < string | null > ( null )
// 密钥显示状态: key_id -> 完整密钥
const revealedKeys = ref < Map < string , string > > ( new Map ( ) )
const revealingKeyId = ref < string | null > ( null )
// 模型相关状态
const modelFormDialogOpen = ref ( false )
@@ -609,14 +587,6 @@ const modelToDelete = ref<Model | null>(null)
const batchAssignDialogOpen = ref ( false )
const modelMappingTabRef = ref < InstanceType < typeof ModelMappingTab > | null > ( null )
// 拖动排序相关状态(旧的端点级别拖拽,保留以兼容)
const dragState = ref ( {
isDragging : false ,
draggedKeyId : null as string | null ,
targetKeyId : null as string | null ,
dragEndpointId : null as string | null
} )
// 密钥列表拖拽排序状态
const keyDragState = ref ( {
isDragging : false ,
@@ -640,7 +610,6 @@ const multiplierSaving = ref(false)
// 任意模态窗口打开时,阻止抽屉被误关闭
const hasBlockingDialogOpen = computed ( ( ) =>
endpointDialogOpen . value ||
deleteEndpointConfirmOpen . value ||
keyFormDialogOpen . value ||
keyPermissionsDialogOpen . value ||
deleteKeyConfirmOpen . value ||
@@ -702,18 +671,15 @@ watch(() => props.open, (newOpen) => {
provider . value = null
endpoints . value = [ ]
providerKeys . value = [ ] // 清空 Provider 级别的 keys
expandedEndpoints . value . clear ( )
// 重置所有对话框状态
endpointDialogOpen . value = false
deleteEndpointConfirmOpen . value = false
keyFormDialogOpen . value = false
keyPermissionsDialogOpen . value = false
deleteKeyConfirmOpen . value = false
batchAssignDialogOpen . value = false
// 重置临时数据
endpointToDelete . value = null
currentEndpoint . value = null
editingKey . value = null
keyToDelete . value = null
@@ -737,15 +703,6 @@ function handleClose() {
}
}
// 切换端点展开/收起
function toggleEndpoint ( endpointId : string ) {
if ( expandedEndpoints . value . has ( endpointId ) ) {
expandedEndpoints . value . delete ( endpointId )
} else {
expandedEndpoints . value . add ( endpointId )
}
}
// 显示端点管理对话框
function showAddEndpointDialog ( ) {
endpointDialogOpen . value = true
@@ -757,27 +714,6 @@ function handleEditEndpoint(_endpoint: ProviderEndpoint) {
endpointDialogOpen . value = true
}
function handleDeleteEndpoint ( endpoint : ProviderEndpoint ) {
endpointToDelete . value = endpoint
deleteEndpointConfirmOpen . value = true
}
async function confirmDeleteEndpoint ( ) {
if ( ! endpointToDelete . value ) return
try {
await deleteEndpointAPI ( endpointToDelete . value . id )
showSuccess ( '端点已删除' )
await loadEndpoints ( )
emit ( 'refresh' )
} catch ( err : any ) {
showError ( err . response ? . data ? . detail || '删除失败' , '错误' )
} finally {
deleteEndpointConfirmOpen . value = false
endpointToDelete . value = null
}
}
async function handleEndpointChanged ( ) {
await Promise . all ( [ loadProvider ( ) , loadEndpoints ( ) ] )
emit ( 'refresh' )
@@ -808,37 +744,18 @@ function handleKeyPermissions(key: EndpointAPIKey) {
keyPermissionsDialogOpen . value = true
}
// 切换密钥显示/隐藏
async function toggleKeyReveal ( key : EndpointAPIKey ) {
if ( revealedKeys . value . has ( key . id ) ) {
// 已显示,隐藏它
revealedKeys . value . delete ( key . id )
return
}
// 未显示,调用 API 获取完整密钥
revealingKeyId . value = key . id
try {
const result = await revealEndpointKey ( key . id )
revealedKeys . value . set ( key . id , result . api _key )
} catch ( err : any ) {
showError ( err . response ? . data ? . detail || '获取密钥失败' , '错误' )
} finally {
revealingKeyId . value = null
}
}
// 复制完整密钥
async function copyFullKey ( key : EndpointAPIKey ) {
// 如果已经显示了,直接复制
if ( revealedKeys . value . has ( key . id ) ) {
copyToClipboard ( revealedKeys . value . get ( key . id ) ! )
const cached = revealedKeys . value . get ( key . id )
if ( cached ) {
copyToClipboard ( cached )
return
}
// 否则先获取再复制
try {
const result = await revealEndpointKey ( key . id )
revealedKeys . value . set ( key . id , result . api _key )
copyToClipboard ( result . api _key )
} catch ( err : any ) {
showError ( err . response ? . data ? . detail || '获取密钥失败' , '错误' )
@@ -878,79 +795,11 @@ async function handleRecoverKey(key: EndpointAPIKey) {
}
}
// 检查端点是否有不健康的密钥
function hasUnhealthyKeys ( endpoint : ProviderEndpointWithKeys ) : boolean {
if ( ! endpoint . keys || endpoint . keys . length === 0 ) return false
return endpoint . keys . some ( key =>
key . circuit _breaker _open ||
( key . health _score !== undefined && key . health _score < 1 )
)
}
// 批量恢复端点下所有密钥的健康状态
async function handleRecoverAllKeys ( endpoint : ProviderEndpointWithKeys ) {
if ( ! endpoint . keys || endpoint . keys . length === 0 ) return
const keysToRecover = endpoint . keys . filter ( key =>
key . circuit _breaker _open ||
( key . health _score !== undefined && key . health _score < 1 )
)
if ( keysToRecover . length === 0 ) {
showSuccess ( '所有密钥已处于健康状态' )
return
}
recoveringEndpointId . value = endpoint . id
let successCount = 0
let failCount = 0
try {
for ( const key of keysToRecover ) {
try {
await recoverKeyHealth ( key . id )
successCount ++
} catch {
failCount ++
}
}
if ( failCount === 0 ) {
showSuccess ( ` 已恢复 ${ successCount } 个密钥的健康状态 ` )
} else {
showSuccess ( ` 恢复完成: ${ successCount } 成功, ${ failCount } 失败 ` )
}
await loadEndpoints ( )
emit ( 'refresh' )
} finally {
recoveringEndpointId . value = null
}
}
async function handleKeyChanged ( ) {
await loadEndpoints ( )
emit ( 'refresh' )
}
// 切换端点启用状态
async function toggleEndpointActive ( endpoint : ProviderEndpointWithKeys ) {
if ( togglingEndpointId . value ) return
togglingEndpointId . value = endpoint . id
try {
const newStatus = ! endpoint . is _active
await updateEndpoint ( endpoint . id , { is _active : newStatus } )
endpoint . is _active = newStatus
showSuccess ( newStatus ? '端点已启用' : '端点已停用' )
emit ( 'refresh' )
} catch ( err : any ) {
showError ( err . response ? . data ? . detail || '操作失败' , '错误' )
} finally {
togglingEndpointId . value = null
}
}
// 切换密钥启用状态
async function toggleKeyActive ( key : EndpointAPIKey ) {
if ( togglingKeyId . value ) return
@@ -1022,123 +871,6 @@ async function confirmDeleteModel() {
}
}
// ===== 拖动排序处理 =====
function handleDragStart ( event : DragEvent , key : EndpointAPIKey , endpoint : ProviderEndpointWithKeys ) {
dragState . value . isDragging = true
dragState . value . draggedKeyId = key . id
dragState . value . dragEndpointId = endpoint . id
if ( event . dataTransfer ) {
event . dataTransfer . effectAllowed = 'move'
}
}
function handleDragEnd ( ) {
dragState . value . isDragging = false
dragState . value . draggedKeyId = null
dragState . value . targetKeyId = null
dragState . value . dragEndpointId = null
}
function handleDragOver ( event : DragEvent , targetKey : EndpointAPIKey ) {
event . preventDefault ( )
if ( event . dataTransfer ) {
event . dataTransfer . dropEffect = 'move'
}
if ( dragState . value . draggedKeyId !== targetKey . id ) {
dragState . value . targetKeyId = targetKey . id
}
}
function handleDragLeave ( ) {
dragState . value . targetKeyId = null
}
async function handleDrop ( event : DragEvent , targetKey : EndpointAPIKey , endpoint : ProviderEndpointWithKeys ) {
event . preventDefault ( )
const draggedKeyId = dragState . value . draggedKeyId
if ( ! draggedKeyId || ! endpoint . keys || draggedKeyId === targetKey . id ) {
handleDragEnd ( )
return
}
// 只允许在同一端点内拖动
if ( dragState . value . dragEndpointId !== endpoint . id ) {
showError ( '不能跨端点拖动密钥' )
handleDragEnd ( )
return
}
const keys = [ ... endpoint . keys ]
const draggedIndex = keys . findIndex ( k => k . id === draggedKeyId )
const targetIndex = keys . findIndex ( k => k . id === targetKey . id )
if ( draggedIndex === - 1 || targetIndex === - 1 ) {
handleDragEnd ( )
return
}
// 记录原始优先级分组(排除被拖动的密钥)
// key: 原始优先级值, value: 密钥ID数组
const originalGroups = new Map < number , string [ ] > ( )
for ( const key of keys ) {
if ( key . id === draggedKeyId ) continue // 被拖动的密钥离开原组
const priority = key . internal _priority ? ? 0
if ( ! originalGroups . has ( priority ) ) {
originalGroups . set ( priority , [ ] )
}
originalGroups . get ( priority ) ! . push ( key . id )
}
// 重排数组
const [ removed ] = keys . splice ( draggedIndex , 1 )
keys . splice ( targetIndex , 0 , removed )
endpoint . keys = keys
// 按新顺序为每个组分配新的优先级
// 同组的密钥保持相同的优先级
const priorities : { key _id : string ; internal _priority : number } [ ] = [ ]
const groupNewPriority = new Map < number , number > ( ) // 原优先级 -> 新优先级
let currentPriority = 0
for ( const key of keys ) {
if ( key . id === draggedKeyId ) {
// 被拖动的密钥是独立的新组,获得当前优先级
priorities . push ( { key _id : key . id , internal _priority : currentPriority } )
currentPriority ++
} else {
const originalPriority = key . internal _priority ? ? 0
if ( groupNewPriority . has ( originalPriority ) ) {
// 这个组已经分配过优先级,使用相同的值
priorities . push ( { key _id : key . id , internal _priority : groupNewPriority . get ( originalPriority ) ! } )
} else {
// 这个组第一次出现,分配新优先级
groupNewPriority . set ( originalPriority , currentPriority )
priorities . push ( { key _id : key . id , internal _priority : currentPriority } )
currentPriority ++
}
}
}
handleDragEnd ( )
// 调用 API 批量更新(使用循环调用 updateProviderKey 替代已废弃的 batchUpdateKeyPriority)
try {
await Promise . all (
priorities . map ( p => updateProviderKey ( p . key _id , { internal _priority : p . internal _priority } ) )
)
showSuccess ( '优先级已更新' )
// 重新加载以获取更新后的数据
await loadEndpoints ( )
emit ( 'refresh' )
} catch ( err : any ) {
showError ( err . response ? . data ? . detail || '更新优先级失败' , '错误' )
// 回滚 - 重新加载
await loadEndpoints ( )
}
}
// ===== 点击编辑优先级 =====
function startEditPriority ( key : EndpointAPIKey ) {
editingPriorityKey . value = key . id
@@ -1386,25 +1118,6 @@ async function handleKeyDrop(event: DragEvent, targetIndex: number) {
}
}
// 格式化探测时间
function formatProbeTime ( probeTime : string ) : string {
if ( ! probeTime ) return '-'
const now = new Date ( )
const probe = new Date ( probeTime )
const diffMs = probe . getTime ( ) - now . getTime ( )
if ( diffMs < 0 ) return '待探测'
const diffMinutes = Math . floor ( diffMs / 60000 )
const diffHours = Math . floor ( diffMinutes / 60 )
const diffDays = Math . floor ( diffHours / 24 )
if ( diffDays > 0 ) return ` ${ diffDays } 天后 `
if ( diffHours > 0 ) return ` ${ diffHours } 小时后 `
if ( diffMinutes > 0 ) return ` ${ diffMinutes } 分钟后 `
return '即将探测'
}
// 获取密钥的 API 格式列表(按指定顺序排序)
function getKeyApiFormats ( key : EndpointAPIKey , endpoint ? : ProviderEndpointWithKeys ) : string [ ] {
let formats : string [ ] = [ ]
@@ -1491,7 +1204,7 @@ function getFormatProbeCountdown(key: EndpointAPIKey, format: string): string {
const now = new Date ( )
const diffMs = nextProbe . getTime ( ) - now . getTime ( )
if ( diffMs > 0 ) {
return ' ' + formatCountdown ( diffMs )
return ` ${ formatCountdown ( diffMs ) } `
} else {
return ' 探测中'
}