feat(pool,ui,trace): 号池按页配额刷新、配额倒计时、Trace 全量候选与 UI 用语统一

- 号池管理支持按当前页 Key 刷新配额,后端 refresh-quota 接口支持 key_ids 参数筛选
- 配额进度条 tooltip 展示重置倒计时,前端解析后端重置时间并实时倒计时
- Provider 选择器在无号池提供商时禁用,切换/刷新后保持选中状态对齐
- 启停账号后立即更新调度标签并刷新列表
- Trace 监控展示全量候选记录,不再过滤 available/unused 状态
- 请求时间线号池节点与 Provider 节点去重
- 全局 UI 用语统一:已停用/已禁用 -> 停用/禁用
- 导航菜单调整模型管理与号池管理顺序
This commit is contained in:
fawney19
2026-03-03 11:32:41 +08:00
parent 022aec5720
commit 7ebce161e8
24 changed files with 359 additions and 75 deletions

View File

@@ -1102,7 +1102,7 @@ function getKeyTooltip(key: RoutingKeyInfo): string {
parts.push(`名称: ${key.name}`)
parts.push(`健康度: ${((key.health_score || 0) * 100).toFixed(0)}%`)
if (!key.is_active) {
parts.push('状态: 禁用')
parts.push('状态: 禁用')
} else if (key.circuit_breaker_open) {
parts.push(`熔断中: ${key.circuit_breaker_formats.join(', ')}`)
}

View File

@@ -32,7 +32,7 @@
variant="secondary"
class="text-xs"
>
停用
停用
</Badge>
</div>
<div class="flex items-center gap-1.5">

View File

@@ -323,7 +323,7 @@
: key.is_active
? 'text-foreground/70 hover:bg-muted hover:text-foreground'
: 'text-muted-foreground hover:bg-muted hover:text-foreground'"
:title="!key.provider_active ? 'Provider 停用' : key.is_active ? '点击停用' : '点击启用'"
:title="!key.provider_active ? 'Provider 停用' : key.is_active ? '点击停用' : '点击启用'"
:disabled="!key.provider_active"
@click.stop="toggleKeyActive(format, key)"
>

View File

@@ -35,7 +35,7 @@
:variant="provider.is_active ? 'default' : 'secondary'"
class="text-xs shrink-0"
>
{{ provider.is_active ? '活跃' : '停用' }}
{{ provider.is_active ? '活跃' : '停用' }}
</Badge>
</div>
<div class="flex items-center gap-1 shrink-0">

View File

@@ -133,7 +133,7 @@
:variant="provider.is_active ? 'success' : 'secondary'"
class="text-xs"
>
{{ provider.is_active ? '活跃' : '停用' }}
{{ provider.is_active ? '活跃' : '停用' }}
</Badge>
</TableCell>
<TableCell

View File

@@ -372,7 +372,7 @@ function getVideoPricingTooltip(model: Model): string {
// 获取状态指示灯样式
function getStatusIndicatorClass(model: Model): string {
if (!model.is_active) {
// 停用 - 灰色
// 停用 - 灰色
return 'bg-gray-400 dark:bg-gray-600'
}
if (model.is_available) {
@@ -386,7 +386,7 @@ function getStatusIndicatorClass(model: Model): string {
// 获取状态提示文本
function getStatusTitle(model: Model): string {
if (!model.is_active) {
return '停用'
return '停用'
}
if (model.is_available) {
return '活跃且可用'

View File

@@ -75,7 +75,7 @@ export function getEndpointTooltip(endpoint: EndpointHealthDetail): string {
switch (status) {
case 'disabled':
return `${format}: 端点禁用`
return `${format}: 端点禁用`
case 'no_keys':
return `${format}: 未配置密钥`
case 'keys_disabled':

View File

@@ -19,7 +19,7 @@ export function useProviderFilters(
const statusFilters: FilterOption[] = [
{ value: 'all', label: '全部状态' },
{ value: 'active', label: '活跃' },
{ value: 'inactive', label: '停用' },
{ value: 'inactive', label: '停用' },
]
const apiFormatFilters: FilterOption[] = [

View File

@@ -754,6 +754,11 @@ const getProviderDisplayName = (attempt: CandidateRecord | null | undefined): st
return providerName ? normalizeProviderName(providerName) : '未知'
}
const normalizeProviderIdentity = (value: unknown): string => {
if (typeof value !== 'string') return ''
return normalizeProviderName(value).trim().toLowerCase()
}
const buildProviderGroups = (items: CandidateRecord[]): NodeGroup[] => {
const groups: NodeGroup[] = []
let currentGroup: NodeGroup | null = null
@@ -829,7 +834,31 @@ const groupedTimeline = computed<NodeGroup[]>(() => {
isPoolGroup: true,
}
return [poolGroup, ...providerGroups]
const poolProviderIds = new Set(
poolAttempts
.map(item => String(item.provider_id || '').trim())
.filter(Boolean),
)
const poolProviderNames = new Set(
poolAttempts
.map(item => normalizeProviderIdentity(item.provider_name))
.filter(Boolean),
)
const dedupedProviderGroups = providerGroups.filter((group) => {
const sameProviderById = group.allAttempts.some((attempt) => {
const providerId = String(attempt.provider_id || '').trim()
return providerId !== '' && poolProviderIds.has(providerId)
})
if (sameProviderById) return false
const groupName = normalizeProviderIdentity(group.primary.provider_name || group.providerName)
if (groupName && poolProviderNames.has(groupName)) return false
return true
})
return [poolGroup, ...dedupedProviderGroups]
})
// 格式转换分界点索引(首个 hasConversion=true 的 group index