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

@@ -70,7 +70,7 @@ class PoolSchedulingDimension(Protocol):
@dataclass(frozen=True, slots=True)
class _ManualEnableDimension:
code: str = "manual_disabled"
label: str = "禁用"
label: str = "禁用"
source: str = "manual"
weight: int = 8

View File

@@ -101,6 +101,7 @@ async def refresh_provider_quota_for_provider(
db: Session,
provider_id: str,
codex_wham_usage_url: str,
key_ids: list[str] | None = None,
) -> dict:
"""刷新 Provider 限额信息(惰性导入实现)。"""
from src.services.provider_keys.key_quota_service import (
@@ -111,4 +112,5 @@ async def refresh_provider_quota_for_provider(
db=db,
provider_id=provider_id,
codex_wham_usage_url=codex_wham_usage_url,
key_ids=key_ids,
)

View File

@@ -64,8 +64,9 @@ async def refresh_provider_quota_for_provider(
db: Session,
provider_id: str,
codex_wham_usage_url: str,
key_ids: list[str] | None = None,
) -> dict:
"""刷新指定 Provider 下所有活跃 Key 的限额信息。"""
"""刷新指定 Provider 的限额信息(默认所有活跃 Key可按 key_ids 限定)"""
provider = db.query(Provider).filter(Provider.id == provider_id).first()
if not provider:
raise NotFoundException(f"Provider {provider_id} 不存在")
@@ -74,21 +75,42 @@ async def refresh_provider_quota_for_provider(
if provider_type not in {ProviderType.CODEX, ProviderType.ANTIGRAVITY, ProviderType.KIRO}:
raise InvalidRequestException("仅支持 Codex / Antigravity / Kiro 类型的 Provider 刷新限额")
keys = (
db.query(ProviderAPIKey)
.filter(
ProviderAPIKey.provider_id == provider_id,
ProviderAPIKey.is_active.is_(True),
)
.all()
selected_key_ids: list[str] | None = None
if key_ids is not None:
deduped: list[str] = []
seen: set[str] = set()
for raw in key_ids:
value = str(raw).strip()
if not value or value in seen:
continue
seen.add(value)
deduped.append(value)
selected_key_ids = deduped
keys_query = db.query(ProviderAPIKey).filter(
ProviderAPIKey.provider_id == provider_id,
)
if selected_key_ids is None:
keys_query = keys_query.filter(ProviderAPIKey.is_active.is_(True))
else:
if not selected_key_ids:
return {
"success": 0,
"failed": 0,
"total": 0,
"results": [],
"message": "未提供可刷新的 Key",
}
keys_query = keys_query.filter(ProviderAPIKey.id.in_(selected_key_ids))
keys = keys_query.all()
if not keys:
return {
"success": 0,
"failed": 0,
"total": 0,
"results": [],
"message": "没有活跃的 Key",
"message": "没有可刷新的 Key",
}
endpoint = _select_refresh_endpoint(provider, provider_type)