mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
feat: Codex 提供商批量刷新限额功能 (#147)
- 后端 (keys.py):新增 POST /providers/{provider_id}/refresh-quota API,向 Codex 提供商的所有活跃 Key 发送测试请求,从响应头解析限额信息并更新数据库
- 前端 (ProviderDetailDrawer.vue):在 Codex 类型提供商的密钥管理区域添加"刷新限额"按钮,带加载动画和结果提示
- 前端 API (keys.ts):新增 refreshProviderQuota 接口调用及 RefreshQuotaResult 类型定义
- 其他:description 字段允许 null,公开 ensure_collectors_registered 函数
This commit is contained in:
@@ -144,3 +144,31 @@ export async function updateProviderKey(
|
||||
const response = await client.put(`/api/admin/endpoints/keys/${keyId}`, data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 Provider 的所有 Key 限额信息(Codex)
|
||||
*/
|
||||
export interface RefreshQuotaResult {
|
||||
success: number
|
||||
failed: number
|
||||
total: number
|
||||
results: Array<{
|
||||
key_id: string
|
||||
key_name: string
|
||||
status: 'success' | 'no_metadata' | 'error'
|
||||
metadata?: {
|
||||
plan_type?: string
|
||||
primary_used_percent?: number
|
||||
primary_reset_seconds?: number
|
||||
secondary_used_percent?: number
|
||||
secondary_reset_seconds?: number
|
||||
}
|
||||
message?: string
|
||||
status_code?: number
|
||||
}>
|
||||
}
|
||||
|
||||
export async function refreshProviderQuota(providerId: string): Promise<RefreshQuotaResult> {
|
||||
const response = await client.post(`/api/admin/endpoints/providers/${providerId}/refresh-quota`)
|
||||
return response.data
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function updateProvider(
|
||||
data: Partial<{
|
||||
name: string
|
||||
provider_type: 'custom' | 'claude_code' | 'codex' | 'gemini_cli' | 'antigravity'
|
||||
description: string
|
||||
description: string | null
|
||||
website: string
|
||||
provider_priority: number
|
||||
billing_type: 'monthly_quota' | 'pay_as_you_go' | 'free_tier'
|
||||
|
||||
@@ -188,16 +188,34 @@
|
||||
<h3 class="text-sm font-semibold">
|
||||
{{ provider.provider_type === 'custom' ? '密钥管理' : '账号管理' }}
|
||||
</h3>
|
||||
<Button
|
||||
v-if="endpoints.length > 0"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="h-8"
|
||||
@click="handleAddKeyToFirstEndpoint"
|
||||
>
|
||||
<Plus class="w-3.5 h-3.5 mr-1.5" />
|
||||
{{ provider.provider_type === 'custom' ? '添加密钥' : '添加账号' }}
|
||||
</Button>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Codex 刷新限额按钮 -->
|
||||
<Button
|
||||
v-if="provider.provider_type === 'codex' && allKeys.length > 0"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="h-8"
|
||||
:disabled="refreshingQuota"
|
||||
title="刷新所有账号的限额信息"
|
||||
@click="handleRefreshQuota"
|
||||
>
|
||||
<RefreshCw
|
||||
class="w-3.5 h-3.5 mr-1.5"
|
||||
:class="{ 'animate-spin': refreshingQuota }"
|
||||
/>
|
||||
刷新限额
|
||||
</Button>
|
||||
<Button
|
||||
v-if="endpoints.length > 0"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="h-8"
|
||||
@click="handleAddKeyToFirstEndpoint"
|
||||
>
|
||||
<Plus class="w-3.5 h-3.5 mr-1.5" />
|
||||
{{ provider.provider_type === 'custom' ? '添加密钥' : '添加账号' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -667,6 +685,7 @@ import {
|
||||
updateProviderKey,
|
||||
revealEndpointKey,
|
||||
refreshProviderOAuth,
|
||||
refreshProviderQuota,
|
||||
type ProviderEndpoint,
|
||||
type EndpointAPIKey,
|
||||
type Model,
|
||||
@@ -746,6 +765,9 @@ const prioritySaving = ref(false)
|
||||
// OAuth 刷新状态
|
||||
const refreshingOAuthKeyId = ref<string | null>(null)
|
||||
|
||||
// Codex 限额刷新状态
|
||||
const refreshingQuota = ref(false)
|
||||
|
||||
// 描述编辑状态
|
||||
const editingDescription = ref(false)
|
||||
const editingDescriptionValue = ref('')
|
||||
@@ -1041,6 +1063,28 @@ async function handleRefreshOAuth(key: EndpointAPIKey) {
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新 Codex 所有账号限额
|
||||
async function handleRefreshQuota() {
|
||||
if (refreshingQuota.value || !props.providerId) return
|
||||
refreshingQuota.value = true
|
||||
try {
|
||||
const result = await refreshProviderQuota(props.providerId)
|
||||
if (result.success > 0) {
|
||||
showSuccess(`成功刷新 ${result.success}/${result.total} 个账号的限额`)
|
||||
// 重新加载数据以更新 UI
|
||||
await loadEndpoints()
|
||||
} else if (result.failed > 0) {
|
||||
showError(`刷新失败: ${result.results.map(r => r.message).filter(Boolean).join(', ')}`, '错误')
|
||||
} else {
|
||||
showError('没有获取到限额信息', '警告')
|
||||
}
|
||||
} catch (err: any) {
|
||||
showError(err.response?.data?.detail || '刷新限额失败', '错误')
|
||||
} finally {
|
||||
refreshingQuota.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleKeyChanged() {
|
||||
await loadEndpoints()
|
||||
// 并行刷新模型列表和模型映射(因为模型权限会影响正则映射预览)
|
||||
|
||||
Reference in New Issue
Block a user