mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-12 04:28:28 +08:00
refactor: 重构限流系统和健康监控,支持按 API 格式区分
- 将 adaptive_concurrency 重命名为 adaptive_rpm,从并发控制改为 RPM 控制 - 健康监控器支持按 API 格式独立管理健康度和熔断器状态 - 新增 model_permissions 模块,支持按格式配置允许的模型 - 重构前端提供商相关表单组件,新增 Collapsible UI 组件 - 新增数据库迁移脚本支持新的数据结构
This commit is contained in:
@@ -530,9 +530,6 @@
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium text-sm truncate">
|
||||
{{ provider.display_name || provider.name }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground truncate">
|
||||
{{ provider.name }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -645,10 +642,7 @@
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium text-sm truncate">
|
||||
{{ provider.display_name }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground truncate">
|
||||
{{ provider.identifier }}
|
||||
{{ provider.name }}
|
||||
</p>
|
||||
</div>
|
||||
<Badge
|
||||
@@ -679,7 +673,7 @@
|
||||
<ProviderModelFormDialog
|
||||
:open="editProviderDialogOpen"
|
||||
:provider-id="editingProvider?.id || ''"
|
||||
:provider-name="editingProvider?.display_name || ''"
|
||||
:provider-name="editingProvider?.name || ''"
|
||||
:editing-model="editingProviderModel"
|
||||
@update:open="handleEditProviderDialogUpdate"
|
||||
@saved="handleEditProviderSaved"
|
||||
@@ -939,7 +933,7 @@ async function batchAddSelectedProviders() {
|
||||
const errorMessages = result.errors
|
||||
.map(e => {
|
||||
const provider = providerOptions.value.find(p => p.id === e.provider_id)
|
||||
const providerName = provider?.display_name || provider?.name || e.provider_id
|
||||
const providerName = provider?.name || e.provider_id
|
||||
return `${providerName}: ${e.error}`
|
||||
})
|
||||
.join('\n')
|
||||
@@ -977,7 +971,7 @@ async function batchRemoveSelectedProviders() {
|
||||
await deleteModel(providerId, provider.model_id)
|
||||
successCount++
|
||||
} catch (err: any) {
|
||||
errors.push(`${provider.display_name}: ${parseApiError(err, '删除失败')}`)
|
||||
errors.push(`${provider.name}: ${parseApiError(err, '删除失败')}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1088,8 +1082,7 @@ async function loadModelProviders(_globalModelId: string) {
|
||||
selectedModelProviders.value = response.providers.map(p => ({
|
||||
id: p.provider_id,
|
||||
model_id: p.model_id,
|
||||
display_name: p.provider_display_name || p.provider_name,
|
||||
identifier: p.provider_name,
|
||||
name: p.provider_name,
|
||||
provider_type: 'API',
|
||||
target_model: p.target_model,
|
||||
is_active: p.is_active,
|
||||
@@ -1219,7 +1212,7 @@ async function confirmDeleteProviderImplementation(provider: any) {
|
||||
}
|
||||
|
||||
const confirmed = await confirmDanger(
|
||||
`确定要删除 ${provider.display_name} 的模型关联吗?\n\n模型: ${provider.target_model}\n\n此操作不可恢复!`,
|
||||
`确定要删除 ${provider.name} 的模型关联吗?\n\n模型: ${provider.target_model}\n\n此操作不可恢复!`,
|
||||
'删除关联提供商'
|
||||
)
|
||||
if (!confirmed) return
|
||||
@@ -1227,7 +1220,7 @@ async function confirmDeleteProviderImplementation(provider: any) {
|
||||
try {
|
||||
const { deleteModel } = await import('@/api/endpoints')
|
||||
await deleteModel(provider.id, provider.model_id)
|
||||
success(`已删除 ${provider.display_name} 的模型实现`)
|
||||
success(`已删除 ${provider.name} 的模型实现`)
|
||||
// 重新加载 Provider 列表
|
||||
if (selectedModel.value) {
|
||||
await loadModelProviders(selectedModel.value.id)
|
||||
|
||||
@@ -134,10 +134,7 @@
|
||||
@click="handleRowClick($event, provider.id)"
|
||||
>
|
||||
<TableCell class="py-3.5">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<span class="text-sm font-medium text-foreground">{{ provider.display_name }}</span>
|
||||
<span class="text-xs text-muted-foreground/70 font-mono">{{ provider.name }}</span>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-foreground">{{ provider.name }}</span>
|
||||
</TableCell>
|
||||
<TableCell class="py-3.5">
|
||||
<Badge
|
||||
@@ -219,17 +216,10 @@
|
||||
>${{ (provider.monthly_used_usd ?? 0).toFixed(2) }}</span> / <span class="font-medium">${{ (provider.monthly_quota_usd ?? 0).toFixed(2) }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="rpmUsage(provider)"
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<span class="text-muted-foreground/70">RPM:</span>
|
||||
<span class="font-medium text-foreground/80">{{ rpmUsage(provider) }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="provider.billing_type !== 'monthly_quota' && !rpmUsage(provider)"
|
||||
v-else
|
||||
class="text-muted-foreground/50"
|
||||
>
|
||||
无限制
|
||||
按量付费
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
@@ -304,7 +294,7 @@
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-foreground truncate">{{ provider.display_name }}</span>
|
||||
<span class="font-medium text-foreground truncate">{{ provider.name }}</span>
|
||||
<Badge
|
||||
:variant="provider.is_active ? 'success' : 'secondary'"
|
||||
class="text-xs shrink-0"
|
||||
@@ -312,7 +302,6 @@
|
||||
{{ provider.is_active ? '活跃' : '停用' }}
|
||||
</Badge>
|
||||
</div>
|
||||
<span class="text-xs text-muted-foreground/70 font-mono">{{ provider.name }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center gap-0.5 shrink-0"
|
||||
@@ -383,20 +372,17 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 第四行:配额/限流 -->
|
||||
<!-- 第四行:配额 -->
|
||||
<div
|
||||
v-if="provider.billing_type === 'monthly_quota' || rpmUsage(provider)"
|
||||
v-if="provider.billing_type === 'monthly_quota'"
|
||||
class="flex items-center gap-3 text-xs text-muted-foreground"
|
||||
>
|
||||
<span v-if="provider.billing_type === 'monthly_quota'">
|
||||
<span>
|
||||
配额: <span
|
||||
class="font-semibold"
|
||||
:class="getQuotaUsedColorClass(provider)"
|
||||
>${{ (provider.monthly_used_usd ?? 0).toFixed(2) }}</span> / ${{ (provider.monthly_quota_usd ?? 0).toFixed(2) }}
|
||||
</span>
|
||||
<span v-if="rpmUsage(provider)">
|
||||
RPM: {{ rpmUsage(provider) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -509,7 +495,7 @@ const filteredProviders = computed(() => {
|
||||
if (searchQuery.value.trim()) {
|
||||
const keywords = searchQuery.value.toLowerCase().split(/\s+/).filter(k => k.length > 0)
|
||||
result = result.filter(p => {
|
||||
const searchableText = `${p.display_name} ${p.name}`.toLowerCase()
|
||||
const searchableText = `${p.name}`.toLowerCase()
|
||||
return keywords.every(keyword => searchableText.includes(keyword))
|
||||
})
|
||||
}
|
||||
@@ -525,7 +511,7 @@ const filteredProviders = computed(() => {
|
||||
return a.provider_priority - b.provider_priority
|
||||
}
|
||||
// 3. 按名称排序
|
||||
return a.display_name.localeCompare(b.display_name)
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -586,7 +572,10 @@ function sortEndpoints(endpoints: any[]) {
|
||||
|
||||
// 判断端点是否可用(有 key)
|
||||
function isEndpointAvailable(endpoint: any, _provider: ProviderWithEndpointsSummary): boolean {
|
||||
// 检查该端点是否有活跃的密钥
|
||||
// 检查端点是否启用,以及是否有活跃的密钥
|
||||
if (endpoint.is_active === false) {
|
||||
return false
|
||||
}
|
||||
return (endpoint.active_keys ?? 0) > 0
|
||||
}
|
||||
|
||||
@@ -639,21 +628,6 @@ function getQuotaUsedColorClass(provider: ProviderWithEndpointsSummary): string
|
||||
return 'text-foreground'
|
||||
}
|
||||
|
||||
function rpmUsage(provider: ProviderWithEndpointsSummary): string | null {
|
||||
const rpmLimit = provider.rpm_limit
|
||||
const rpmUsed = provider.rpm_used ?? 0
|
||||
|
||||
if (rpmLimit === null || rpmLimit === undefined) {
|
||||
return rpmUsed > 0 ? `${rpmUsed}` : null
|
||||
}
|
||||
|
||||
if (rpmLimit === 0) {
|
||||
return '已完全禁止'
|
||||
}
|
||||
|
||||
return `${rpmUsed} / ${rpmLimit}`
|
||||
}
|
||||
|
||||
// 使用复用的行点击逻辑
|
||||
const { handleMouseDown, shouldTriggerRowClick } = useRowClick()
|
||||
|
||||
@@ -706,7 +680,7 @@ function handleProviderAdded() {
|
||||
async function handleDeleteProvider(provider: ProviderWithEndpointsSummary) {
|
||||
const confirmed = await confirmDanger(
|
||||
'删除提供商',
|
||||
`确定要删除提供商 "${provider.display_name}" 吗?\n\n这将同时删除其所有端点、密钥和配置。此操作不可恢复!`
|
||||
`确定要删除提供商 "${provider.name}" 吗?\n\n这将同时删除其所有端点、密钥和配置。此操作不可恢复!`
|
||||
)
|
||||
|
||||
if (!confirmed) return
|
||||
|
||||
@@ -511,7 +511,7 @@
|
||||
端点: {{ importPreview.providers?.reduce((sum: number, p: any) => sum + (p.endpoints?.length || 0), 0) }} 个
|
||||
</li>
|
||||
<li>
|
||||
API Keys: {{ importPreview.providers?.reduce((sum: number, p: any) => sum + p.endpoints?.reduce((s: number, e: any) => s + (e.keys?.length || 0), 0), 0) }} 个
|
||||
API Keys: {{ importPreview.providers?.reduce((sum: number, p: any) => sum + (p.api_keys?.length || 0), 0) }} 个
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1144,7 +1144,7 @@ function handleConfigFileSelect(event: Event) {
|
||||
const data = JSON.parse(content) as ConfigExportData
|
||||
|
||||
// 验证版本
|
||||
if (data.version !== '1.0') {
|
||||
if (data.version !== '2.0') {
|
||||
error(`不支持的配置版本: ${data.version}`)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user