mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 01:40:21 +08:00
feat(usage,pool,codex): 请求详情 body 按需加载、配额选择器重构与 Codex body rules 修正
- usage 详情 API 新增 include_bodies 参数,支持跳过 body 内容返回 has_*_body 标记 - 前端请求详情抽屉首次加载不含 body,切换到 body tab 时延迟加载并展示 skeleton - Timeline 组件延迟 120ms 挂载,避免阻塞抽屉渲染 - 提取号池配额判断逻辑到 quota-selectors 工具模块并添加单测 - Codex 移除 openai:compact 的默认 body rules 注册 - ModelTestDialog/TestResultDialog 模板格式化
This commit is contained in:
@@ -270,6 +270,7 @@ import { listPoolKeys, type PoolKeyDetail } from '@/api/endpoints/pool'
|
||||
import { deleteEndpointKey, refreshProviderQuota, updateProviderKey } from '@/api/endpoints/keys'
|
||||
import { refreshProviderOAuth } from '@/api/endpoints/provider_oauth'
|
||||
import { useProxyNodesStore } from '@/stores/proxy-nodes'
|
||||
import { hasNoFiveHourLimit as hasNoFiveHourLimitByQuota, hasNoWeeklyLimit as hasNoWeeklyLimitByQuota } from '@/features/pool/utils/quota-selectors'
|
||||
|
||||
type QuickSelectorValue =
|
||||
| 'banned'
|
||||
@@ -410,31 +411,12 @@ function isBannedKey(key: PoolKeyDetail): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
function getQuotaSegments(accountQuota: string | null | undefined): string[] {
|
||||
return String(accountQuota || '')
|
||||
.split('|')
|
||||
.map((segment) => normalizeText(segment))
|
||||
.filter(Boolean)
|
||||
function hasNoFiveHourQuota(key: PoolKeyDetail): boolean {
|
||||
return hasNoFiveHourLimitByQuota(key.account_quota)
|
||||
}
|
||||
|
||||
function isDepletedQuotaSegment(segment: string): boolean {
|
||||
if (/(无额度|额度不足|已耗尽|耗尽|depleted|exhausted|insufficient)/.test(segment)) return true
|
||||
if (/剩余\s*0(\.0+)?/.test(segment)) return true
|
||||
if (/\b0(\.0+)?\s*\/\s*\d/.test(segment)) return true
|
||||
if (/\b0(\.0+)?%/.test(segment)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
function hasNoFiveHourLimit(key: PoolKeyDetail): boolean {
|
||||
return getQuotaSegments(key.account_quota)
|
||||
.filter((segment) => /5h|5小时/.test(segment))
|
||||
.some(isDepletedQuotaSegment)
|
||||
}
|
||||
|
||||
function hasNoWeeklyLimit(key: PoolKeyDetail): boolean {
|
||||
return getQuotaSegments(key.account_quota)
|
||||
.filter((segment) => /周|weekly|week/.test(segment))
|
||||
.some(isDepletedQuotaSegment)
|
||||
function hasNoWeeklyQuota(key: PoolKeyDetail): boolean {
|
||||
return hasNoWeeklyLimitByQuota(key.account_quota)
|
||||
}
|
||||
|
||||
function isOAuthInvalid(key: PoolKeyDetail): boolean {
|
||||
@@ -474,8 +456,8 @@ function toggleSelectFiltered(checked: boolean | 'indeterminate'): void {
|
||||
|
||||
function matchesSelector(key: PoolKeyDetail, selector: QuickSelectorValue): boolean {
|
||||
if (selector === 'banned') return isBannedKey(key)
|
||||
if (selector === 'no_5h_limit') return hasNoFiveHourLimit(key)
|
||||
if (selector === 'no_weekly_limit') return hasNoWeeklyLimit(key)
|
||||
if (selector === 'no_5h_limit') return hasNoFiveHourQuota(key)
|
||||
if (selector === 'no_weekly_limit') return hasNoWeeklyQuota(key)
|
||||
if (selector === 'plan_free') return isFreePlan(key)
|
||||
if (selector === 'plan_team') return isTeamPlan(key)
|
||||
if (selector === 'oauth_invalid') return isOAuthInvalid(key)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
getQuotaSegments,
|
||||
hasNoFiveHourLimit,
|
||||
hasNoWeeklyLimit,
|
||||
isDepletedQuotaSegment,
|
||||
} from '../quota-selectors'
|
||||
|
||||
describe('quota selectors', () => {
|
||||
it('splits quota text into normalized segments', () => {
|
||||
expect(getQuotaSegments('周剩余 0.0% | 5H剩余 100.0%')).toEqual([
|
||||
'周剩余 0.0%',
|
||||
'5h剩余 100.0%',
|
||||
])
|
||||
})
|
||||
|
||||
it('treats exact 0 percent as depleted', () => {
|
||||
expect(isDepletedQuotaSegment('周剩余 0.0%(5天后重置)')).toBe(true)
|
||||
expect(isDepletedQuotaSegment('5h剩余 0%')).toBe(true)
|
||||
})
|
||||
|
||||
it('does not treat non-zero decimal percentages as depleted', () => {
|
||||
expect(isDepletedQuotaSegment('周45.0% 5d20h')).toBe(false)
|
||||
expect(isDepletedQuotaSegment('周17.0% 5d20h')).toBe(false)
|
||||
expect(isDepletedQuotaSegment('5h93.0% 2h')).toBe(false)
|
||||
})
|
||||
|
||||
it('detects only depleted weekly segments', () => {
|
||||
expect(hasNoWeeklyLimit('周剩余 0.0%(5天后重置) | 5H剩余 93.0%(2小时后重置)')).toBe(true)
|
||||
expect(hasNoWeeklyLimit('周剩余 45.0%(5天后重置) | 5H剩余 0.0%(2小时后重置)')).toBe(false)
|
||||
})
|
||||
|
||||
it('detects only depleted 5h segments', () => {
|
||||
expect(hasNoFiveHourLimit('周剩余 45.0%(5天后重置) | 5H剩余 0.0%(2小时后重置)')).toBe(true)
|
||||
expect(hasNoFiveHourLimit('周剩余 0.0%(5天后重置) | 5H剩余 93.0%(2小时后重置)')).toBe(false)
|
||||
})
|
||||
})
|
||||
51
frontend/src/features/pool/utils/quota-selectors.ts
Normal file
51
frontend/src/features/pool/utils/quota-selectors.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
export function normalizeQuotaSegment(value: string | null | undefined): string {
|
||||
return String(value || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/%/g, '%')
|
||||
}
|
||||
|
||||
export function getQuotaSegments(accountQuota: string | null | undefined): string[] {
|
||||
return String(accountQuota || '')
|
||||
.split('|')
|
||||
.map((segment) => normalizeQuotaSegment(segment))
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
function hasDepletedKeyword(segment: string): boolean {
|
||||
return /(无额度|额度不足|已耗尽|耗尽|depleted|exhausted|insufficient)/.test(segment)
|
||||
}
|
||||
|
||||
function hasZeroRemainingText(segment: string): boolean {
|
||||
return /剩余\s*0(?:\.0+)?(?!\d)/.test(segment)
|
||||
}
|
||||
|
||||
function hasZeroRatio(segment: string): boolean {
|
||||
return [...segment.matchAll(/(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)/g)]
|
||||
.some(([, used, total]) => Number(used) === 0 && Number(total) > 0)
|
||||
}
|
||||
|
||||
function hasZeroPercent(segment: string): boolean {
|
||||
return [...segment.matchAll(/(\d+(?:\.\d+)?)\s*%/g)]
|
||||
.some(([, percent]) => Number(percent) === 0)
|
||||
}
|
||||
|
||||
export function isDepletedQuotaSegment(segment: string): boolean {
|
||||
if (hasDepletedKeyword(segment)) return true
|
||||
if (hasZeroRemainingText(segment)) return true
|
||||
if (hasZeroRatio(segment)) return true
|
||||
if (hasZeroPercent(segment)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
export function hasNoFiveHourLimit(accountQuota: string | null | undefined): boolean {
|
||||
return getQuotaSegments(accountQuota)
|
||||
.filter((segment) => /5h|5小时/.test(segment))
|
||||
.some(isDepletedQuotaSegment)
|
||||
}
|
||||
|
||||
export function hasNoWeeklyLimit(accountQuota: string | null | undefined): boolean {
|
||||
return getQuotaSegments(accountQuota)
|
||||
.filter((segment) => /周|weekly|week/.test(segment))
|
||||
.some(isDepletedQuotaSegment)
|
||||
}
|
||||
Reference in New Issue
Block a user