@@ -7,11 +7,14 @@ import {
77 IconSatellite ,
88 IconTimer ,
99 IconTrendingUp ,
10+ IconChartLine ,
11+ IconDownload ,
1012} from '@/components/ui/icons' ;
1113import {
1214 LATENCY_SOURCE_FIELD ,
1315 calculateLatencyStatsFromDetails ,
1416 calculateCost ,
17+ formatBytes ,
1518 formatCompactNumber ,
1619 formatDurationMs ,
1720 formatPerMinuteValue ,
@@ -60,7 +63,7 @@ export function StatCards({ usage, loading, modelPrices, nowMs, sparklines }: St
6063
6164 const hasPrices = Object . keys ( modelPrices ) . length > 0 ;
6265
63- const { tokenBreakdown, rateStats, totalCost, latencyStats } = useMemo ( ( ) => {
66+ const { tokenBreakdown, rateStats, totalCost, latencyStats, requestMetrics } = useMemo ( ( ) => {
6467 const empty = {
6568 tokenBreakdown : { cachedTokens : 0 , reasoningTokens : 0 } ,
6669 rateStats : { rpm : 0 , tpm : 0 , windowMinutes : 30 , requestCount : 0 , tokenCount : 0 } ,
@@ -70,6 +73,11 @@ export function StatCards({ usage, loading, modelPrices, nowMs, sparklines }: St
7073 totalMs : null as number | null ,
7174 sampleCount : 0 ,
7275 } ,
76+ requestMetrics : {
77+ chunkCount : 0 ,
78+ responseBytes : 0 ,
79+ apiResponseBytes : 0 ,
80+ } ,
7381 } ;
7482
7583 if ( ! usage ) return empty ;
@@ -81,6 +89,9 @@ export function StatCards({ usage, loading, modelPrices, nowMs, sparklines }: St
8189 let cachedTokens = 0 ;
8290 let reasoningTokens = 0 ;
8391 let totalCost = 0 ;
92+ let chunkCount = 0 ;
93+ let responseBytes = 0 ;
94+ let apiResponseBytes = 0 ;
8495
8596 const now = nowMs ;
8697 const windowMinutes = 30 ;
@@ -98,6 +109,18 @@ export function StatCards({ usage, loading, modelPrices, nowMs, sparklines }: St
98109 if ( typeof tokens . reasoning_tokens === 'number' ) {
99110 reasoningTokens += tokens . reasoning_tokens ;
100111 }
112+ chunkCount +=
113+ typeof detail . chunk_count === 'number' && Number . isFinite ( detail . chunk_count )
114+ ? Math . max ( detail . chunk_count , 0 )
115+ : 0 ;
116+ responseBytes +=
117+ typeof detail . response_bytes === 'number' && Number . isFinite ( detail . response_bytes )
118+ ? Math . max ( detail . response_bytes , 0 )
119+ : 0 ;
120+ apiResponseBytes +=
121+ typeof detail . api_response_bytes === 'number' && Number . isFinite ( detail . api_response_bytes )
122+ ? Math . max ( detail . api_response_bytes , 0 )
123+ : 0 ;
101124
102125 const timestamp = detail . __timestampMs ?? 0 ;
103126 if (
@@ -127,6 +150,11 @@ export function StatCards({ usage, loading, modelPrices, nowMs, sparklines }: St
127150 } ,
128151 totalCost,
129152 latencyStats,
153+ requestMetrics : {
154+ chunkCount,
155+ responseBytes,
156+ apiResponseBytes,
157+ } ,
130158 } ;
131159 } , [ hasPrices , modelPrices , nowMs , usage ] ) ;
132160
@@ -151,14 +179,58 @@ export function StatCards({ usage, loading, modelPrices, nowMs, sparklines }: St
151179 </ span >
152180 { latencyStats . sampleCount > 0 && (
153181 < span className = { styles . statMetaItem } title = { latencyHint } >
154- { t ( 'usage_stats.avg_time' ) } :{ ' ' }
155- { loading ? '-' : formatDurationMs ( latencyStats . averageMs ) }
182+ { t ( 'usage_stats.avg_time' ) } : { loading ? '-' : formatDurationMs ( latencyStats . averageMs ) }
156183 </ span >
157184 ) }
158185 </ >
159186 ) ,
160187 trend : sparklines . requests ,
161188 } ,
189+ {
190+ key : 'chunks' ,
191+ label : t ( 'usage_stats.total_chunk_count' ) ,
192+ icon : < IconChartLine size = { 16 } /> ,
193+ accent : '#0ea5e9' ,
194+ accentSoft : 'rgba(14, 165, 233, 0.18)' ,
195+ accentBorder : 'rgba(14, 165, 233, 0.32)' ,
196+ value : loading ? '-' : formatCompactNumber ( requestMetrics . chunkCount ) ,
197+ meta : (
198+ < >
199+ < span className = { styles . statMetaItem } >
200+ { t ( 'usage_stats.total_requests' ) } : { loading ? '-' : ( usage ?. total_requests ?? 0 ) . toLocaleString ( ) }
201+ </ span >
202+ < span className = { styles . statMetaItem } >
203+ { t ( 'usage_stats.avg_chunk_per_request' ) } :{ ' ' }
204+ { loading
205+ ? '-'
206+ : ( usage ?. total_requests ?? 0 ) > 0
207+ ? ( requestMetrics . chunkCount / Math . max ( usage ?. total_requests ?? 0 , 1 ) ) . toFixed ( 1 )
208+ : '0.0' }
209+ </ span >
210+ </ >
211+ ) ,
212+ trend : null ,
213+ } ,
214+ {
215+ key : 'traffic' ,
216+ label : t ( 'usage_stats.traffic_stats' ) ,
217+ icon : < IconDownload size = { 16 } /> ,
218+ accent : '#14b8a6' ,
219+ accentSoft : 'rgba(20, 184, 166, 0.18)' ,
220+ accentBorder : 'rgba(20, 184, 166, 0.32)' ,
221+ value : loading ? '-' : formatBytes ( requestMetrics . responseBytes + requestMetrics . apiResponseBytes ) ,
222+ meta : (
223+ < >
224+ < span className = { styles . statMetaItem } >
225+ { t ( 'usage_stats.response_bytes' ) } : { loading ? '-' : formatBytes ( requestMetrics . responseBytes ) }
226+ </ span >
227+ < span className = { styles . statMetaItem } >
228+ { t ( 'usage_stats.api_response_bytes' ) } : { loading ? '-' : formatBytes ( requestMetrics . apiResponseBytes ) }
229+ </ span >
230+ </ >
231+ ) ,
232+ trend : null ,
233+ } ,
162234 {
163235 key : 'tokens' ,
164236 label : t ( 'usage_stats.total_tokens' ) ,
@@ -170,12 +242,10 @@ export function StatCards({ usage, loading, modelPrices, nowMs, sparklines }: St
170242 meta : (
171243 < >
172244 < span className = { styles . statMetaItem } >
173- { t ( 'usage_stats.cached_tokens' ) } :{ ' ' }
174- { loading ? '-' : formatCompactNumber ( tokenBreakdown . cachedTokens ) }
245+ { t ( 'usage_stats.cached_tokens' ) } : { loading ? '-' : formatCompactNumber ( tokenBreakdown . cachedTokens ) }
175246 </ span >
176247 < span className = { styles . statMetaItem } >
177- { t ( 'usage_stats.reasoning_tokens' ) } :{ ' ' }
178- { loading ? '-' : formatCompactNumber ( tokenBreakdown . reasoningTokens ) }
248+ { t ( 'usage_stats.reasoning_tokens' ) } : { loading ? '-' : formatCompactNumber ( tokenBreakdown . reasoningTokens ) }
179249 </ span >
180250 </ >
181251 ) ,
@@ -191,8 +261,7 @@ export function StatCards({ usage, loading, modelPrices, nowMs, sparklines }: St
191261 value : loading ? '-' : formatPerMinuteValue ( rateStats . rpm ) ,
192262 meta : (
193263 < span className = { styles . statMetaItem } >
194- { t ( 'usage_stats.total_requests' ) } :{ ' ' }
195- { loading ? '-' : rateStats . requestCount . toLocaleString ( ) }
264+ { t ( 'usage_stats.total_requests' ) } : { loading ? '-' : rateStats . requestCount . toLocaleString ( ) }
196265 </ span >
197266 ) ,
198267 trend : sparklines . rpm ,
@@ -207,8 +276,7 @@ export function StatCards({ usage, loading, modelPrices, nowMs, sparklines }: St
207276 value : loading ? '-' : formatPerMinuteValue ( rateStats . tpm ) ,
208277 meta : (
209278 < span className = { styles . statMetaItem } >
210- { t ( 'usage_stats.total_tokens' ) } :{ ' ' }
211- { loading ? '-' : formatCompactNumber ( rateStats . tokenCount ) }
279+ { t ( 'usage_stats.total_tokens' ) } : { loading ? '-' : formatCompactNumber ( rateStats . tokenCount ) }
212280 </ span >
213281 ) ,
214282 trend : sparklines . tpm ,
@@ -224,8 +292,7 @@ export function StatCards({ usage, loading, modelPrices, nowMs, sparklines }: St
224292 meta : (
225293 < >
226294 < span className = { styles . statMetaItem } >
227- { t ( 'usage_stats.total_tokens' ) } :{ ' ' }
228- { loading ? '-' : formatCompactNumber ( usage ?. total_tokens ?? 0 ) }
295+ { t ( 'usage_stats.total_tokens' ) } : { loading ? '-' : formatCompactNumber ( usage ?. total_tokens ?? 0 ) }
229296 </ span >
230297 { ! hasPrices && (
231298 < span className = { `${ styles . statMetaItem } ${ styles . statSubtle } ` } >
0 commit comments