From d79a06460f6302a929453b0c20c9fdd079a01025 Mon Sep 17 00:00:00 2001 From: Liyan Zhao Date: Sat, 30 May 2026 18:38:06 +0800 Subject: [PATCH] fix(monitoring): populate model price candidates --- .../monitoring/modelPriceCandidates.ts | 17 +++++++++ src/pages/MonitoringCenterPage.test.tsx | 36 +++++++++++++++++++ src/pages/MonitoringCenterPage.tsx | 7 ++-- 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 src/features/monitoring/modelPriceCandidates.ts diff --git a/src/features/monitoring/modelPriceCandidates.ts b/src/features/monitoring/modelPriceCandidates.ts new file mode 100644 index 000000000..df6b4f603 --- /dev/null +++ b/src/features/monitoring/modelPriceCandidates.ts @@ -0,0 +1,17 @@ +import type { MonitoringEventRow } from './hooks/useMonitoringData'; +import type { ModelPrice } from '@/utils/usage'; + +export const buildModelPriceCandidateModels = ( + facetModels: readonly string[], + rows: readonly Pick[], + modelPrices: Record +) => + Array.from( + new Set([ + ...facetModels.map((model) => model.trim()), + ...rows.map((row) => row.model.trim()), + ...Object.keys(modelPrices).map((model) => model.trim()), + ]) + ) + .filter(Boolean) + .sort((left, right) => left.localeCompare(right)); diff --git a/src/pages/MonitoringCenterPage.test.tsx b/src/pages/MonitoringCenterPage.test.tsx index 1e3c2fe99..c4d6d6e92 100644 --- a/src/pages/MonitoringCenterPage.test.tsx +++ b/src/pages/MonitoringCenterPage.test.tsx @@ -8,6 +8,7 @@ import { } from './MonitoringCenterPage'; import { buildEmptyMonitoringStatusData } from '@/features/monitoring/accountOverviewState'; import { buildRealtimeSourceDisplay } from '@/features/monitoring/realtimeSourceDisplay'; +import { buildModelPriceCandidateModels } from '@/features/monitoring/modelPriceCandidates'; import type { MonitoringEventRow } from '@/features/monitoring/hooks/useMonitoringData'; const t = ((key: string, options?: Record) => { @@ -129,6 +130,41 @@ const createMonitoringEventRow = ( }); describe('MonitoringCenterPage account card', () => { + it('builds model price candidates from summary facets when detail rows are empty', () => { + expect( + buildModelPriceCandidateModels( + [' gpt-5 ', 'gemini-2.5-flash', ''], + [], + { + 'claude-sonnet-4-5': { + prompt: 3, + completion: 15, + cache: 0.3, + }, + } + ) + ).toEqual(['claude-sonnet-4-5', 'gemini-2.5-flash', 'gpt-5']); + }); + + it('keeps legacy row and saved-price model candidates available', () => { + expect( + buildModelPriceCandidateModels( + [], + [ + createMonitoringEventRow({ model: 'gpt-4.1' }), + createMonitoringEventRow({ id: 'row-2', model: ' gpt-4.1 ' }), + ], + { + 'codex-mini-latest': { + prompt: 1, + completion: 4, + cache: 0.1, + }, + } + ) + ).toEqual(['codex-mini-latest', 'gpt-4.1']); + }); + it('uses server realtime stream identities without duplicating final totals per row', () => { const rows = buildRealtimeLogRows([ createMonitoringEventRow({ diff --git a/src/pages/MonitoringCenterPage.tsx b/src/pages/MonitoringCenterPage.tsx index 7934a968e..a7325dc21 100644 --- a/src/pages/MonitoringCenterPage.tsx +++ b/src/pages/MonitoringCenterPage.tsx @@ -87,6 +87,7 @@ import { getNextMonitoringStatusBlockIndex, } from '@/features/monitoring/healthStatusAccessibility'; import { buildRealtimeSourceDisplay } from '@/features/monitoring/realtimeSourceDisplay'; +import { buildModelPriceCandidateModels } from '@/features/monitoring/modelPriceCandidates'; import { MonitoringPanel } from '@/features/monitoring/components/MonitoringPanel'; import { useUsageData } from '@/features/monitoring/hooks/useUsageData'; import { useHeaderRefresh } from '@/hooks/useHeaderRefresh'; @@ -2269,10 +2270,8 @@ export function MonitoringCenterPage() { const syncPriceModels = useMemo( () => - Array.from(new Set([...filteredRows.map((row) => row.model), ...Object.keys(modelPrices)])) - .filter(Boolean) - .sort((left, right) => left.localeCompare(right)), - [filteredRows, modelPrices] + buildModelPriceCandidateModels(filterFacets.models, filteredRows, modelPrices), + [filterFacets.models, filteredRows, modelPrices] ); const priceModelOptions = useMemo(