mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 17:30:23 +08:00
Merge branch 'pr-390' into aether-rust-pioneer
This commit is contained in:
@@ -39,6 +39,7 @@ describe('poolManagementState', () => {
|
||||
pageSize: 20,
|
||||
sortBy: 'last_used_at',
|
||||
sortOrder: 'asc',
|
||||
statsMode: 'account_total',
|
||||
},
|
||||
storage,
|
||||
)
|
||||
@@ -52,6 +53,7 @@ describe('poolManagementState', () => {
|
||||
pageSize: '100',
|
||||
sortBy: 'imported_at',
|
||||
sortOrder: 'desc',
|
||||
statsMode: 'current_cycle',
|
||||
},
|
||||
storage,
|
||||
)
|
||||
@@ -64,6 +66,7 @@ describe('poolManagementState', () => {
|
||||
pageSize: 100,
|
||||
sortBy: 'imported_at',
|
||||
sortOrder: 'desc',
|
||||
statsMode: 'current_cycle',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -77,6 +80,7 @@ describe('poolManagementState', () => {
|
||||
pageSize: 50,
|
||||
sortBy: 'last_used_at',
|
||||
sortOrder: 'asc',
|
||||
statsMode: 'account_total',
|
||||
},
|
||||
storage,
|
||||
)
|
||||
@@ -91,6 +95,7 @@ describe('poolManagementState', () => {
|
||||
pageSize: 50,
|
||||
sortBy: 'last_used_at',
|
||||
sortOrder: 'asc',
|
||||
statsMode: 'account_total',
|
||||
})
|
||||
})
|
||||
|
||||
@@ -104,6 +109,7 @@ describe('poolManagementState', () => {
|
||||
pageSize: 50,
|
||||
sortBy: null,
|
||||
sortOrder: 'desc',
|
||||
statsMode: 'current_cycle',
|
||||
}),
|
||||
).toEqual({
|
||||
providerId: 'provider-d',
|
||||
@@ -113,6 +119,7 @@ describe('poolManagementState', () => {
|
||||
pageSize: undefined,
|
||||
sortBy: undefined,
|
||||
sortOrder: undefined,
|
||||
statsMode: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -126,13 +133,36 @@ describe('poolManagementState', () => {
|
||||
pageSize: 50,
|
||||
sortBy: 'last_used_at',
|
||||
sortOrder: 'asc',
|
||||
statsMode: 'account_total',
|
||||
}),
|
||||
).toMatchObject({
|
||||
sortBy: 'last_used_at',
|
||||
sortOrder: 'asc',
|
||||
statsMode: 'account_total',
|
||||
})
|
||||
})
|
||||
|
||||
it('restores stats mode from storage and lets query override it', () => {
|
||||
writePoolManagementViewState(
|
||||
{
|
||||
providerId: 'provider-f',
|
||||
search: '',
|
||||
status: 'all',
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
sortBy: null,
|
||||
sortOrder: 'desc',
|
||||
statsMode: 'account_total',
|
||||
},
|
||||
storage,
|
||||
)
|
||||
|
||||
expect(readPoolManagementViewState({}, storage).statsMode).toBe('account_total')
|
||||
expect(
|
||||
readPoolManagementViewState({ statsMode: 'current_cycle' }, storage).statsMode,
|
||||
).toBe('current_cycle')
|
||||
})
|
||||
|
||||
it('clamps a restored page to the last available page after load', () => {
|
||||
expect(
|
||||
resolvePoolManagementPageAfterLoad({
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
buildPoolStatsDisplay,
|
||||
type PoolStatsKeyInput,
|
||||
} from '@/features/pool/utils/poolStatsDisplay'
|
||||
|
||||
function metricValues(metrics: Array<{ key: string, value: string }>) {
|
||||
return Object.fromEntries(metrics.map(metric => [metric.key, metric.value]))
|
||||
}
|
||||
|
||||
function createCodexKey(overrides: Partial<PoolStatsKeyInput> = {}): PoolStatsKeyInput {
|
||||
return {
|
||||
request_count: 1234,
|
||||
total_tokens: 5678000,
|
||||
total_cost_usd: '12.3456',
|
||||
status_snapshot: {
|
||||
quota: {
|
||||
windows: [
|
||||
{
|
||||
code: '5h',
|
||||
usage: {
|
||||
request_count: 5,
|
||||
total_tokens: 2500,
|
||||
total_cost_usd: '0.0045',
|
||||
},
|
||||
},
|
||||
{
|
||||
code: 'weekly',
|
||||
usage: {
|
||||
request_count: 0,
|
||||
total_tokens: 0,
|
||||
total_cost_usd: '0.00000000',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
describe('poolStatsDisplay', () => {
|
||||
it('builds Codex current-cycle groups in 5H and weekly order', () => {
|
||||
const display = buildPoolStatsDisplay(createCodexKey(), 'codex', 'current_cycle')
|
||||
|
||||
expect(display.kind).toBe('codex_cycle')
|
||||
if (display.kind !== 'codex_cycle') throw new Error('expected codex cycle display')
|
||||
|
||||
expect(display.groups.map(group => group.label)).toEqual(['5H', '周'])
|
||||
expect(metricValues(display.groups[0].metrics)).toEqual({
|
||||
request_count: '5',
|
||||
total_tokens: '2.5K',
|
||||
total_cost_usd: '$0.0045',
|
||||
})
|
||||
expect(metricValues(display.groups[1].metrics)).toEqual({
|
||||
request_count: '0',
|
||||
total_tokens: '0',
|
||||
total_cost_usd: '0',
|
||||
})
|
||||
})
|
||||
|
||||
it('renders missing cycle usage as dashes instead of account-total fallback', () => {
|
||||
const display = buildPoolStatsDisplay(
|
||||
createCodexKey({
|
||||
status_snapshot: {
|
||||
quota: {
|
||||
windows: [{ code: '5h', usage: null }],
|
||||
},
|
||||
},
|
||||
}),
|
||||
'codex',
|
||||
'current_cycle',
|
||||
)
|
||||
|
||||
expect(display.kind).toBe('codex_cycle')
|
||||
if (display.kind !== 'codex_cycle') throw new Error('expected codex cycle display')
|
||||
|
||||
expect(metricValues(display.groups[0].metrics)).toEqual({
|
||||
request_count: '—',
|
||||
total_tokens: '—',
|
||||
total_cost_usd: '—',
|
||||
})
|
||||
expect(metricValues(display.groups[1].metrics)).toEqual({
|
||||
request_count: '—',
|
||||
total_tokens: '—',
|
||||
total_cost_usd: '—',
|
||||
})
|
||||
})
|
||||
|
||||
it('preserves account-total formatting when toggled away from current cycle', () => {
|
||||
const display = buildPoolStatsDisplay(createCodexKey(), 'codex', 'account_total')
|
||||
|
||||
expect(display.kind).toBe('account_total')
|
||||
if (display.kind !== 'account_total') throw new Error('expected account total display')
|
||||
|
||||
expect(metricValues(display.metrics)).toEqual({
|
||||
request_count: '1,234',
|
||||
total_tokens: '5.7M',
|
||||
total_cost_usd: '$12.35',
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps non-Codex providers on account totals even in current-cycle mode', () => {
|
||||
const display = buildPoolStatsDisplay(createCodexKey(), 'openai', 'current_cycle')
|
||||
|
||||
expect(display.kind).toBe('account_total')
|
||||
if (display.kind !== 'account_total') throw new Error('expected account total display')
|
||||
expect(metricValues(display.metrics)).toMatchObject({
|
||||
request_count: '1,234',
|
||||
total_tokens: '5.7M',
|
||||
total_cost_usd: '$12.35',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
export type PoolManagementStatus = 'all' | 'active' | 'cooldown' | 'inactive'
|
||||
export type PoolManagementSortBy = 'imported_at' | 'last_used_at'
|
||||
export type PoolManagementSortOrder = 'asc' | 'desc'
|
||||
export type PoolManagementStatsMode = 'current_cycle' | 'account_total'
|
||||
|
||||
export interface PoolManagementViewState {
|
||||
providerId: string | null
|
||||
@@ -10,6 +11,7 @@ export interface PoolManagementViewState {
|
||||
pageSize: number
|
||||
sortBy: PoolManagementSortBy | null
|
||||
sortOrder: PoolManagementSortOrder
|
||||
statsMode: PoolManagementStatsMode
|
||||
}
|
||||
|
||||
export interface PoolManagementStateSource {
|
||||
@@ -20,6 +22,7 @@ export interface PoolManagementStateSource {
|
||||
pageSize?: string
|
||||
sortBy?: string
|
||||
sortOrder?: string
|
||||
statsMode?: string
|
||||
}
|
||||
|
||||
export interface StorageLike {
|
||||
@@ -28,6 +31,10 @@ export interface StorageLike {
|
||||
removeItem(key: string): void
|
||||
}
|
||||
|
||||
type PoolManagementViewStateInput = Partial<{
|
||||
[Key in keyof PoolManagementViewState]: unknown
|
||||
}>
|
||||
|
||||
export const POOL_MANAGEMENT_VIEW_STORAGE_KEY = 'aether:pool-management:view-state'
|
||||
|
||||
export const DEFAULT_POOL_MANAGEMENT_VIEW_STATE: PoolManagementViewState = {
|
||||
@@ -38,6 +45,7 @@ export const DEFAULT_POOL_MANAGEMENT_VIEW_STATE: PoolManagementViewState = {
|
||||
pageSize: 50,
|
||||
sortBy: null,
|
||||
sortOrder: 'desc',
|
||||
statsMode: 'current_cycle',
|
||||
}
|
||||
|
||||
function normalizeProviderId(value: unknown): string | null {
|
||||
@@ -75,7 +83,11 @@ function normalizeSortOrder(value: unknown): PoolManagementSortOrder {
|
||||
return value === 'asc' ? 'asc' : 'desc'
|
||||
}
|
||||
|
||||
function normalizeViewState(input: Partial<PoolManagementViewState>): PoolManagementViewState {
|
||||
function normalizeStatsMode(value: unknown): PoolManagementStatsMode {
|
||||
return value === 'account_total' ? 'account_total' : 'current_cycle'
|
||||
}
|
||||
|
||||
function normalizeViewState(input: PoolManagementViewStateInput): PoolManagementViewState {
|
||||
return {
|
||||
providerId: normalizeProviderId(input.providerId),
|
||||
search: normalizeSearch(input.search),
|
||||
@@ -84,6 +96,7 @@ function normalizeViewState(input: Partial<PoolManagementViewState>): PoolManage
|
||||
pageSize: normalizePositiveInteger(input.pageSize, DEFAULT_POOL_MANAGEMENT_VIEW_STATE.pageSize),
|
||||
sortBy: normalizeSortBy(input.sortBy),
|
||||
sortOrder: normalizeSortOrder(input.sortOrder),
|
||||
statsMode: normalizeStatsMode(input.statsMode),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,15 +119,20 @@ export function readPoolManagementViewState(
|
||||
): PoolManagementViewState {
|
||||
const stored = normalizeViewState(readStoredState(storage))
|
||||
|
||||
return normalizeViewState({
|
||||
providerId: source.providerId ?? stored.providerId,
|
||||
search: source.search ?? stored.search,
|
||||
status: source.status ?? stored.status,
|
||||
page: source.page ?? stored.page,
|
||||
pageSize: source.pageSize ?? stored.pageSize,
|
||||
sortBy: source.sortBy ?? stored.sortBy,
|
||||
sortOrder: source.sortOrder ?? stored.sortOrder,
|
||||
})
|
||||
return {
|
||||
providerId: source.providerId !== undefined ? normalizeProviderId(source.providerId) : stored.providerId,
|
||||
search: source.search !== undefined ? normalizeSearch(source.search) : stored.search,
|
||||
status: source.status !== undefined ? normalizeStatus(source.status) : stored.status,
|
||||
page: source.page !== undefined
|
||||
? normalizePositiveInteger(source.page, DEFAULT_POOL_MANAGEMENT_VIEW_STATE.page)
|
||||
: stored.page,
|
||||
pageSize: source.pageSize !== undefined
|
||||
? normalizePositiveInteger(source.pageSize, DEFAULT_POOL_MANAGEMENT_VIEW_STATE.pageSize)
|
||||
: stored.pageSize,
|
||||
sortBy: source.sortBy !== undefined ? normalizeSortBy(source.sortBy) : stored.sortBy,
|
||||
sortOrder: source.sortOrder !== undefined ? normalizeSortOrder(source.sortOrder) : stored.sortOrder,
|
||||
statsMode: source.statsMode !== undefined ? normalizeStatsMode(source.statsMode) : stored.statsMode,
|
||||
}
|
||||
}
|
||||
|
||||
export function writePoolManagementViewState(
|
||||
@@ -150,6 +168,7 @@ export function buildPoolManagementQueryPatch(
|
||||
: String(normalized.pageSize),
|
||||
sortBy: normalized.sortBy || undefined,
|
||||
sortOrder: normalized.sortBy ? normalized.sortOrder : undefined,
|
||||
statsMode: normalized.statsMode === 'account_total' ? 'account_total' : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
178
frontend/src/features/pool/utils/poolStatsDisplay.ts
Normal file
178
frontend/src/features/pool/utils/poolStatsDisplay.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import type { QuotaWindowUsageSnapshot } from '@/api/endpoints/types/statusSnapshot'
|
||||
import type { PoolManagementStatsMode } from '@/features/pool/utils/poolManagementState'
|
||||
|
||||
export type PoolStatsMetricKey = 'request_count' | 'total_tokens' | 'total_cost_usd'
|
||||
export type PoolStatsDisplayKind = 'account_total' | 'codex_cycle'
|
||||
export type PoolCodexCycleWindowCode = '5h' | 'weekly'
|
||||
|
||||
export interface PoolStatsKeyInput {
|
||||
request_count?: number | null
|
||||
total_tokens?: number | null
|
||||
total_cost_usd?: number | string | null
|
||||
status_snapshot?: {
|
||||
quota?: {
|
||||
windows?: Array<{
|
||||
code?: string | null
|
||||
usage?: QuotaWindowUsageSnapshot | null
|
||||
} | null> | null
|
||||
} | null
|
||||
} | null
|
||||
}
|
||||
|
||||
export interface PoolStatsMetric {
|
||||
key: PoolStatsMetricKey
|
||||
label: string
|
||||
value: string
|
||||
missing: boolean
|
||||
}
|
||||
|
||||
export interface PoolAccountTotalStatsDisplay {
|
||||
kind: 'account_total'
|
||||
metrics: PoolStatsMetric[]
|
||||
}
|
||||
|
||||
export interface PoolCodexCycleStatsGroup {
|
||||
code: PoolCodexCycleWindowCode
|
||||
label: string
|
||||
metrics: PoolStatsMetric[]
|
||||
}
|
||||
|
||||
export interface PoolCodexCycleStatsDisplay {
|
||||
kind: 'codex_cycle'
|
||||
groups: PoolCodexCycleStatsGroup[]
|
||||
}
|
||||
|
||||
export type PoolStatsDisplay = PoolAccountTotalStatsDisplay | PoolCodexCycleStatsDisplay
|
||||
|
||||
const MISSING_STAT_VALUE = '—'
|
||||
const CODEX_CYCLE_WINDOWS: Array<{ code: PoolCodexCycleWindowCode, label: string }> = [
|
||||
{ code: '5h', label: '5H' },
|
||||
{ code: 'weekly', label: '周' },
|
||||
]
|
||||
|
||||
export function isCodexProviderType(providerType: string | null | undefined): boolean {
|
||||
return String(providerType || '').trim().toLowerCase() === 'codex'
|
||||
}
|
||||
|
||||
export function formatPoolStatInteger(value: number | null | undefined): string {
|
||||
const n = Number(value ?? 0)
|
||||
if (!Number.isFinite(n) || n <= 0) return '0'
|
||||
return Math.round(n).toLocaleString('en-US')
|
||||
}
|
||||
|
||||
export function formatPoolTokenCount(value: number | null | undefined): string {
|
||||
const n = Number(value ?? 0)
|
||||
if (!Number.isFinite(n) || n <= 0) return '0'
|
||||
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
|
||||
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`
|
||||
return String(Math.round(n))
|
||||
}
|
||||
|
||||
export function formatPoolStatUsd(value: number | string | null | undefined): string {
|
||||
const n = Number(value ?? 0)
|
||||
if (!Number.isFinite(n) || n <= 0) return '$0.00'
|
||||
if (n < 0.01) return `$${n.toFixed(4)}`
|
||||
if (n < 1) return `$${n.toFixed(3)}`
|
||||
if (n < 1000) return `$${n.toFixed(2)}`
|
||||
return `$${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
||||
}
|
||||
|
||||
function formatCycleInteger(value: number | null | undefined): string | null {
|
||||
if (value == null) return null
|
||||
const n = Number(value)
|
||||
if (!Number.isFinite(n)) return null
|
||||
if (n <= 0) return '0'
|
||||
return Math.round(n).toLocaleString('en-US')
|
||||
}
|
||||
|
||||
function formatCycleTokenCount(value: number | null | undefined): string | null {
|
||||
if (value == null) return null
|
||||
const n = Number(value)
|
||||
if (!Number.isFinite(n)) return null
|
||||
return formatPoolTokenCount(n)
|
||||
}
|
||||
|
||||
function formatCycleUsd(value: number | string | null | undefined): string | null {
|
||||
if (value == null) return null
|
||||
const n = Number(value)
|
||||
if (!Number.isFinite(n)) return null
|
||||
if (n <= 0) return '0'
|
||||
return formatPoolStatUsd(value)
|
||||
}
|
||||
|
||||
function createMetric(
|
||||
key: PoolStatsMetricKey,
|
||||
label: string,
|
||||
value: string | null,
|
||||
): PoolStatsMetric {
|
||||
return {
|
||||
key,
|
||||
label,
|
||||
value: value ?? MISSING_STAT_VALUE,
|
||||
missing: value == null,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeWindowCode(value: unknown): string {
|
||||
return String(value || '').trim().toLowerCase()
|
||||
}
|
||||
|
||||
function getQuotaWindowUsage(
|
||||
key: PoolStatsKeyInput,
|
||||
code: PoolCodexCycleWindowCode,
|
||||
): QuotaWindowUsageSnapshot | null {
|
||||
const windows = key.status_snapshot?.quota?.windows
|
||||
if (!Array.isArray(windows)) return null
|
||||
|
||||
const window = windows.find(item => normalizeWindowCode(item?.code) === code)
|
||||
return window?.usage ?? null
|
||||
}
|
||||
|
||||
function buildAccountTotalMetrics(key: PoolStatsKeyInput): PoolStatsMetric[] {
|
||||
return [
|
||||
createMetric('request_count', '请求', formatPoolStatInteger(key.request_count)),
|
||||
createMetric('total_tokens', 'Token', formatPoolTokenCount(key.total_tokens)),
|
||||
createMetric('total_cost_usd', '费用', formatPoolStatUsd(key.total_cost_usd)),
|
||||
]
|
||||
}
|
||||
|
||||
function buildCycleMetrics(usage: QuotaWindowUsageSnapshot | null): PoolStatsMetric[] {
|
||||
return [
|
||||
createMetric('request_count', '请求', formatCycleInteger(usage?.request_count)),
|
||||
createMetric('total_tokens', 'Token', formatCycleTokenCount(usage?.total_tokens)),
|
||||
createMetric('total_cost_usd', '费用', formatCycleUsd(usage?.total_cost_usd)),
|
||||
]
|
||||
}
|
||||
|
||||
export function buildAccountTotalStatsDisplay(
|
||||
key: PoolStatsKeyInput,
|
||||
): PoolAccountTotalStatsDisplay {
|
||||
return {
|
||||
kind: 'account_total',
|
||||
metrics: buildAccountTotalMetrics(key),
|
||||
}
|
||||
}
|
||||
|
||||
export function buildCodexCycleStatsDisplay(
|
||||
key: PoolStatsKeyInput,
|
||||
): PoolCodexCycleStatsDisplay {
|
||||
return {
|
||||
kind: 'codex_cycle',
|
||||
groups: CODEX_CYCLE_WINDOWS.map(window => ({
|
||||
...window,
|
||||
metrics: buildCycleMetrics(getQuotaWindowUsage(key, window.code)),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export function buildPoolStatsDisplay(
|
||||
key: PoolStatsKeyInput,
|
||||
providerType: string | null | undefined,
|
||||
mode: PoolManagementStatsMode,
|
||||
): PoolStatsDisplay {
|
||||
if (isCodexProviderType(providerType) && mode === 'current_cycle') {
|
||||
return buildCodexCycleStatsDisplay(key)
|
||||
}
|
||||
|
||||
return buildAccountTotalStatsDisplay(key)
|
||||
}
|
||||
Reference in New Issue
Block a user