mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-03 00:02:28 +08:00
refactor: optimize provider query and stats aggregation logic
This commit is contained in:
@@ -124,6 +124,27 @@ export interface ModelExport {
|
||||
config?: any
|
||||
}
|
||||
|
||||
// Provider 模型查询响应
|
||||
export interface ProviderModelsQueryResponse {
|
||||
success: boolean
|
||||
data: {
|
||||
models: Array<{
|
||||
id: string
|
||||
object?: string
|
||||
created?: number
|
||||
owned_by?: string
|
||||
display_name?: string
|
||||
api_format?: string
|
||||
}>
|
||||
error?: string
|
||||
}
|
||||
provider: {
|
||||
id: string
|
||||
name: string
|
||||
display_name: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ConfigImportRequest extends ConfigExportData {
|
||||
merge_mode: 'skip' | 'overwrite' | 'error'
|
||||
}
|
||||
@@ -356,5 +377,14 @@ export const adminApi = {
|
||||
data
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
// 查询 Provider 可用模型(从上游 API 获取)
|
||||
async queryProviderModels(providerId: string, apiKeyId?: string): Promise<ProviderModelsQueryResponse> {
|
||||
const response = await apiClient.post<ProviderModelsQueryResponse>(
|
||||
'/api/admin/provider-query/models',
|
||||
{ provider_id: providerId, api_key_id: apiKeyId }
|
||||
)
|
||||
return response.data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
// API 格式常量
|
||||
export const API_FORMATS = {
|
||||
CLAUDE: 'CLAUDE',
|
||||
CLAUDE_CLI: 'CLAUDE_CLI',
|
||||
OPENAI: 'OPENAI',
|
||||
OPENAI_CLI: 'OPENAI_CLI',
|
||||
GEMINI: 'GEMINI',
|
||||
GEMINI_CLI: 'GEMINI_CLI',
|
||||
} as const
|
||||
|
||||
export type APIFormat = typeof API_FORMATS[keyof typeof API_FORMATS]
|
||||
|
||||
// API 格式显示名称映射(按品牌分组:API 在前,CLI 在后)
|
||||
export const API_FORMAT_LABELS: Record<string, string> = {
|
||||
[API_FORMATS.CLAUDE]: 'Claude',
|
||||
[API_FORMATS.CLAUDE_CLI]: 'Claude CLI',
|
||||
[API_FORMATS.OPENAI]: 'OpenAI',
|
||||
[API_FORMATS.OPENAI_CLI]: 'OpenAI CLI',
|
||||
[API_FORMATS.GEMINI]: 'Gemini',
|
||||
[API_FORMATS.GEMINI_CLI]: 'Gemini CLI',
|
||||
}
|
||||
|
||||
export interface ProviderEndpoint {
|
||||
id: string
|
||||
provider_id: string
|
||||
@@ -214,6 +236,7 @@ export interface ConcurrencyStatus {
|
||||
export interface ProviderModelAlias {
|
||||
name: string
|
||||
priority: number // 优先级(数字越小优先级越高)
|
||||
api_formats?: string[] // 作用域(适用的 API 格式),为空表示对所有格式生效
|
||||
}
|
||||
|
||||
export interface Model {
|
||||
|
||||
@@ -396,15 +396,13 @@ interface ProviderGroup {
|
||||
|
||||
const groupedModels = computed(() => {
|
||||
let models = allModels.value.filter(m => !m.deprecated)
|
||||
// 搜索(支持空格分隔的多关键词 AND 搜索)
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
models = models.filter(model =>
|
||||
model.providerId.toLowerCase().includes(query) ||
|
||||
model.providerName.toLowerCase().includes(query) ||
|
||||
model.modelId.toLowerCase().includes(query) ||
|
||||
model.modelName.toLowerCase().includes(query) ||
|
||||
model.family?.toLowerCase().includes(query)
|
||||
)
|
||||
const keywords = searchQuery.value.toLowerCase().split(/\s+/).filter(k => k.length > 0)
|
||||
models = models.filter(model => {
|
||||
const searchableText = `${model.providerId} ${model.providerName} ${model.modelId} ${model.modelName} ${model.family || ''}`.toLowerCase()
|
||||
return keywords.every(keyword => searchableText.includes(keyword))
|
||||
})
|
||||
}
|
||||
|
||||
// 按提供商分组
|
||||
@@ -425,10 +423,12 @@ const groupedModels = computed(() => {
|
||||
|
||||
// 如果有搜索词,把提供商名称/ID匹配的排在前面
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
const keywords = searchQuery.value.toLowerCase().split(/\s+/).filter(k => k.length > 0)
|
||||
result.sort((a, b) => {
|
||||
const aProviderMatch = a.providerId.toLowerCase().includes(query) || a.providerName.toLowerCase().includes(query)
|
||||
const bProviderMatch = b.providerId.toLowerCase().includes(query) || b.providerName.toLowerCase().includes(query)
|
||||
const aText = `${a.providerId} ${a.providerName}`.toLowerCase()
|
||||
const bText = `${b.providerId} ${b.providerName}`.toLowerCase()
|
||||
const aProviderMatch = keywords.some(k => aText.includes(k))
|
||||
const bProviderMatch = keywords.some(k => bText.includes(k))
|
||||
if (aProviderMatch && !bProviderMatch) return -1
|
||||
if (!aProviderMatch && bProviderMatch) return 1
|
||||
return a.providerName.localeCompare(b.providerName)
|
||||
|
||||
@@ -526,7 +526,14 @@
|
||||
@edit-model="handleEditModel"
|
||||
@delete-model="handleDeleteModel"
|
||||
@batch-assign="handleBatchAssign"
|
||||
@manage-alias="handleManageAlias"
|
||||
/>
|
||||
|
||||
<!-- 模型名称映射 -->
|
||||
<ModelAliasesTab
|
||||
v-if="provider"
|
||||
:key="`aliases-${provider.id}`"
|
||||
:provider="provider"
|
||||
@refresh="handleRelatedDataRefresh"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -629,16 +636,6 @@
|
||||
@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">
|
||||
@@ -667,8 +664,8 @@ import {
|
||||
KeyFormDialog,
|
||||
KeyAllowedModelsDialog,
|
||||
ModelsTab,
|
||||
BatchAssignModelsDialog,
|
||||
ModelAliasDialog
|
||||
ModelAliasesTab,
|
||||
BatchAssignModelsDialog
|
||||
} from '@/features/providers/components'
|
||||
import EndpointFormDialog from '@/features/providers/components/EndpointFormDialog.vue'
|
||||
import ProviderModelFormDialog from '@/features/providers/components/ProviderModelFormDialog.vue'
|
||||
@@ -737,10 +734,6 @@ 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,
|
||||
@@ -762,8 +755,7 @@ const hasBlockingDialogOpen = computed(() =>
|
||||
deleteKeyConfirmOpen.value ||
|
||||
modelFormDialogOpen.value ||
|
||||
deleteModelConfirmOpen.value ||
|
||||
batchAssignDialogOpen.value ||
|
||||
aliasDialogOpen.value
|
||||
batchAssignDialogOpen.value
|
||||
)
|
||||
|
||||
// 监听 providerId 变化
|
||||
@@ -792,7 +784,6 @@ watch(() => props.open, (newOpen) => {
|
||||
keyAllowedModelsDialogOpen.value = false
|
||||
deleteKeyConfirmOpen.value = false
|
||||
batchAssignDialogOpen.value = false
|
||||
aliasDialogOpen.value = false
|
||||
|
||||
// 重置临时数据
|
||||
endpointToEdit.value = null
|
||||
@@ -1030,19 +1021,6 @@ 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
|
||||
|
||||
@@ -10,3 +10,4 @@ export { default as BatchAssignModelsDialog } from './BatchAssignModelsDialog.vu
|
||||
export { default as ModelAliasDialog } from './ModelAliasDialog.vue'
|
||||
|
||||
export { default as ModelsTab } from './provider-tabs/ModelsTab.vue'
|
||||
export { default as ModelAliasesTab } from './provider-tabs/ModelAliasesTab.vue'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -165,15 +165,6 @@
|
||||
>
|
||||
<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"
|
||||
@@ -218,7 +209,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { Box, Edit, Trash2, Layers, Eye, Wrench, Zap, Brain, Power, Copy, Image, Tag } from 'lucide-vue-next'
|
||||
import { Box, Edit, Trash2, Layers, Eye, Wrench, Zap, Brain, Power, Copy, Image } from 'lucide-vue-next'
|
||||
import Card from '@/components/ui/card.vue'
|
||||
import Button from '@/components/ui/button.vue'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
@@ -233,7 +224,6 @@ const emit = defineEmits<{
|
||||
'editModel': [model: Model]
|
||||
'deleteModel': [model: Model]
|
||||
'batchAssign': []
|
||||
'manageAlias': [model: Model]
|
||||
}>()
|
||||
|
||||
const { error: showError, success: showSuccess } = useToast()
|
||||
@@ -373,11 +363,6 @@ function openBatchAssignDialog() {
|
||||
emit('batchAssign')
|
||||
}
|
||||
|
||||
// 打开别名管理对话框
|
||||
function openAliasDialog(model: Model) {
|
||||
emit('manageAlias', model)
|
||||
}
|
||||
|
||||
// 切换模型启用状态
|
||||
async function toggleModelActive(model: Model) {
|
||||
if (togglingModelId.value) return
|
||||
|
||||
@@ -751,15 +751,13 @@ const expiringSoonCount = computed(() => apiKeys.value.filter(key => isExpiringS
|
||||
const filteredApiKeys = computed(() => {
|
||||
let result = apiKeys.value
|
||||
|
||||
// 搜索筛选
|
||||
// 搜索筛选(支持空格分隔的多关键词 AND 搜索)
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
result = result.filter(key =>
|
||||
(key.name && key.name.toLowerCase().includes(query)) ||
|
||||
(key.key_display && key.key_display.toLowerCase().includes(query)) ||
|
||||
(key.username && key.username.toLowerCase().includes(query)) ||
|
||||
(key.user_email && key.user_email.toLowerCase().includes(query))
|
||||
)
|
||||
const keywords = searchQuery.value.toLowerCase().split(/\s+/).filter(k => k.length > 0)
|
||||
result = result.filter(key => {
|
||||
const searchableText = `${key.name || ''} ${key.key_display || ''} ${key.username || ''} ${key.user_email || ''}`.toLowerCase()
|
||||
return keywords.every(keyword => searchableText.includes(keyword))
|
||||
})
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
|
||||
@@ -1002,13 +1002,13 @@ async function batchRemoveSelectedProviders() {
|
||||
const filteredGlobalModels = computed(() => {
|
||||
let result = globalModels.value
|
||||
|
||||
// 搜索
|
||||
// 搜索(支持空格分隔的多关键词 AND 搜索)
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
result = result.filter(m =>
|
||||
m.name.toLowerCase().includes(query) ||
|
||||
m.display_name?.toLowerCase().includes(query)
|
||||
)
|
||||
const keywords = searchQuery.value.toLowerCase().split(/\s+/).filter(k => k.length > 0)
|
||||
result = result.filter(m => {
|
||||
const searchableText = `${m.name} ${m.display_name || ''}`.toLowerCase()
|
||||
return keywords.every(keyword => searchableText.includes(keyword))
|
||||
})
|
||||
}
|
||||
|
||||
// 能力筛选
|
||||
|
||||
@@ -505,13 +505,13 @@ const priorityModeConfig = computed(() => {
|
||||
const filteredProviders = computed(() => {
|
||||
let result = [...providers.value]
|
||||
|
||||
// 搜索筛选
|
||||
// 搜索筛选(支持空格分隔的多关键词 AND 搜索)
|
||||
if (searchQuery.value.trim()) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
result = result.filter(p =>
|
||||
p.display_name.toLowerCase().includes(query) ||
|
||||
p.name.toLowerCase().includes(query)
|
||||
)
|
||||
const keywords = searchQuery.value.toLowerCase().split(/\s+/).filter(k => k.length > 0)
|
||||
result = result.filter(p => {
|
||||
const searchableText = `${p.display_name} ${p.name}`.toLowerCase()
|
||||
return keywords.every(keyword => searchableText.includes(keyword))
|
||||
})
|
||||
}
|
||||
|
||||
// 排序
|
||||
|
||||
@@ -791,11 +791,13 @@ const filteredUsers = computed(() => {
|
||||
return new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
})
|
||||
|
||||
// 搜索(支持空格分隔的多关键词 AND 搜索)
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
filtered = filtered.filter(
|
||||
u => u.username.toLowerCase().includes(query) || u.email?.toLowerCase().includes(query)
|
||||
)
|
||||
const keywords = searchQuery.value.toLowerCase().split(/\s+/).filter(k => k.length > 0)
|
||||
filtered = filtered.filter(u => {
|
||||
const searchableText = `${u.username} ${u.email || ''}`.toLowerCase()
|
||||
return keywords.every(keyword => searchableText.includes(keyword))
|
||||
})
|
||||
}
|
||||
|
||||
if (filterRole.value !== 'all') {
|
||||
|
||||
@@ -474,13 +474,13 @@ async function toggleCapability(modelName: string, capName: string) {
|
||||
const filteredModels = computed(() => {
|
||||
let result = models.value
|
||||
|
||||
// 搜索
|
||||
// 搜索(支持空格分隔的多关键词 AND 搜索)
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
result = result.filter(m =>
|
||||
m.name.toLowerCase().includes(query) ||
|
||||
m.display_name?.toLowerCase().includes(query)
|
||||
)
|
||||
const keywords = searchQuery.value.toLowerCase().split(/\s+/).filter(k => k.length > 0)
|
||||
result = result.filter(m => {
|
||||
const searchableText = `${m.name} ${m.display_name || ''}`.toLowerCase()
|
||||
return keywords.every(keyword => searchableText.includes(keyword))
|
||||
})
|
||||
}
|
||||
|
||||
// 能力筛选
|
||||
|
||||
Reference in New Issue
Block a user