mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-11 03:39:49 +08:00
refactor: 提取正则工具模块并清理废弃代码
前端: - 新增 model-mapping-regex.ts 工具模块,统一正则验证和 LRU 缓存 - ModelMappingsTab/RoutingTab 重构为使用 computed 缓存匹配结果 - 移除多个组件中未使用的变量和函数 - 添加 HTMLImageElement/HTMLIFrameElement 到 ESLint 全局类型 - 修复 vitest 需要 --experimental-require-module 的问题 后端: - 移除废弃的异步数据库支持 (get_async_db, AsyncSession) - 将 async_utils 从 database/ 迁移到 utils/ - 改进 database.py 类型标注 - 修复 email 模块的 aiosmtplib 可选导入类型问题
This commit is contained in:
@@ -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 ' 探测中'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user