feat(test,quota,failover): 模型并发测试、统一配额读取器与故障转移取消支持

- 新增 QuotaReader 抽象层,统一 Codex/Kiro/Antigravity 配额解析逻辑,
  替换 pool/routes.py 中分散的配额构建函数
- 模型测试支持并发执行多候选,前端新增 useModelTest composable 统一
  ModelsTab 和 ModelMappingTab 的测试逻辑
- ModelTestDialog 增加结果概览摘要、超长结果折叠、端点列和新状态支持,
  删除已合并的 TestResultDialog
- FailoverEngine 新增客户端断开检测,支持取消剩余候选并标记记录
- 刷新配额改为分批执行,直连测试候选按可用性排序
- 修复 error 判断从 "error" in dict 改为 dict.get("error") 避免误判
This commit is contained in:
fawney19
2026-03-07 02:16:03 +08:00
parent 1f3693d3a2
commit fb1aeb789a
22 changed files with 2145 additions and 987 deletions

View File

@@ -202,7 +202,11 @@ export async function refreshProviderQuota(
keyIds?: string[],
): Promise<RefreshQuotaResult> {
const body = keyIds && keyIds.length > 0 ? { key_ids: keyIds } : undefined
const response = await client.post(`/api/admin/endpoints/providers/${providerId}/refresh-quota`, body)
const response = await client.post(
`/api/admin/endpoints/providers/${providerId}/refresh-quota`,
body,
{ timeout: 5 * 60 * 1000 },
)
return response.data
}

View File

@@ -148,6 +148,7 @@ export interface TestModelFailoverRequest {
endpoint_id?: string
message?: string
request_id?: string
concurrency?: number
}
export interface TestAttemptDetail {
@@ -159,7 +160,7 @@ export interface TestAttemptDetail {
key_id: string
auth_type: string
effective_model?: string | null
status: 'success' | 'failed' | 'skipped'
status: 'success' | 'failed' | 'skipped' | 'cancelled' | 'pending' | 'streaming' | 'stream_interrupted' | 'available' | 'unused'
skip_reason?: string | null
error_message?: string | null
status_code?: number | null
@@ -177,9 +178,13 @@ export interface TestModelFailoverResponse {
error?: string | null
}
export async function testModelFailover(data: TestModelFailoverRequest): Promise<TestModelFailoverResponse> {
export async function testModelFailover(
data: TestModelFailoverRequest,
options: { signal?: AbortSignal } = {}
): Promise<TestModelFailoverResponse> {
const response = await client.post('/api/admin/provider-query/test-model-failover', data, {
timeout: 10 * 60 * 1000,
signal: options.signal,
})
return response.data
}