@@ -89,37 +89,54 @@ interface HeatmapCell {
8989 dayOfWeek : number ;
9090}
9191
92- function buildHeatmap ( days : UsageDay [ ] ) : { weeks : HeatmapCell [ ] [ ] ; buckets : number [ ] } {
92+ function buildHeatmap ( days : UsageDay [ ] ) : { weeks : HeatmapCell [ ] [ ] ; months : { label : string ; col : number } [ ] ; buckets : number [ ] } {
9393 const buckets = quantileBuckets ( days . map ( d => d . requests ) ) ;
94- const cells : HeatmapCell [ ] = days . map ( d => {
95- const dt = new Date ( `${ d . date } T00:00:00` ) ;
96- return {
97- date : d . date ,
98- requests : d . requests ,
99- totalTokens : d . totalTokens ,
100- level : bucketLevel ( d . requests , buckets ) ,
101- dayOfWeek : dt . getDay ( ) ,
102- } ;
103- } ) ;
104- if ( cells . length === 0 ) return { weeks : [ ] , buckets } ;
94+ const dayMap = new Map ( days . map ( d => [ d . date , d ] ) ) ;
95+
96+ const today = new Date ( ) ;
97+ today . setHours ( 0 , 0 , 0 , 0 ) ;
98+ const start = new Date ( today ) ;
99+ start . setDate ( start . getDate ( ) - 364 ) ;
100+ // Align to Sunday
101+ start . setDate ( start . getDate ( ) - start . getDay ( ) ) ;
102+
105103 const weeks : HeatmapCell [ ] [ ] = [ ] ;
106- let week : HeatmapCell [ ] = new Array ( cells [ 0 ] . dayOfWeek ) . fill ( null ) . map ( ( ) => ( {
107- date : "" , requests : 0 , totalTokens : 0 , level : 0 , dayOfWeek : 0 ,
108- } ) ) ;
109- for ( const cell of cells ) {
110- week . push ( cell ) ;
111- if ( cell . dayOfWeek === 6 ) {
104+ const months : { label : string ; col : number } [ ] = [ ] ;
105+ const monthNames = [ "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec" ] ;
106+ let lastMonthCol = - 4 ;
107+ let prevMonthIdx = - 1 ;
108+ let week : HeatmapCell [ ] = [ ] ;
109+ const cursor = new Date ( start ) ;
110+
111+ while ( cursor <= today ) {
112+ const iso = `${ cursor . getFullYear ( ) } -${ String ( cursor . getMonth ( ) + 1 ) . padStart ( 2 , "0" ) } -${ String ( cursor . getDate ( ) ) . padStart ( 2 , "0" ) } ` ;
113+ const m = cursor . getMonth ( ) ;
114+ if ( cursor . getDay ( ) === 0 && m !== prevMonthIdx && weeks . length - lastMonthCol >= 4 ) {
115+ months . push ( { label : monthNames [ m ] , col : weeks . length } ) ;
116+ lastMonthCol = weeks . length ;
117+ prevMonthIdx = m ;
118+ }
119+ const d = dayMap . get ( iso ) ;
120+ week . push ( {
121+ date : iso ,
122+ requests : d ?. requests ?? 0 ,
123+ totalTokens : d ?. totalTokens ?? 0 ,
124+ level : d ? bucketLevel ( d . requests , buckets ) : 0 ,
125+ dayOfWeek : cursor . getDay ( ) ,
126+ } ) ;
127+ if ( cursor . getDay ( ) === 6 ) {
112128 weeks . push ( week ) ;
113129 week = [ ] ;
114130 }
131+ cursor . setDate ( cursor . getDate ( ) + 1 ) ;
115132 }
116133 if ( week . length > 0 ) {
117134 while ( week . length < 7 ) {
118135 week . push ( { date : "" , requests : 0 , totalTokens : 0 , level : 0 , dayOfWeek : week . length } ) ;
119136 }
120137 weeks . push ( week ) ;
121138 }
122- return { weeks, buckets } ;
139+ return { weeks, months , buckets } ;
123140}
124141
125142export default function Usage ( { apiBase } : { apiBase : string } ) {
@@ -194,17 +211,28 @@ export default function Usage({ apiBase }: { apiBase: string }) {
194211 < section className = "panel" style = { { marginTop : 16 } } >
195212 < h3 className = "panel-title" > { t ( "usage.section.heatmap" ) } </ h3 >
196213 < div className = "heatmap" >
197- < div className = "heatmap-grid" style = { { gridTemplateColumns : `repeat(${ heatmap . weeks . length } , 12px)` } } >
198- { heatmap . weeks . map ( ( week , wi ) => (
199- < div key = { wi } className = "heatmap-week" >
200- { week . map ( ( cell , di ) => (
201- < div key = { `${ wi } -${ di } ` }
202- className = { `heatmap-cell heatmap-cell-${ cell . level } ` }
203- title = { cell . date ? `${ cell . date } : ${ cell . requests } req · ${ formatTokens ( cell . totalTokens ) } tokens` : "" } />
204- ) ) }
205- </ div >
214+ < div className = "heatmap-months" style = { { gridTemplateColumns : `28px repeat(${ heatmap . weeks . length } , 1fr)` } } >
215+ < span className = "heatmap-day-spacer" />
216+ { heatmap . months . map ( ( m , i ) => (
217+ < span key = { i } className = "heatmap-month" style = { { gridColumn : m . col + 2 } } > { m . label } </ span >
206218 ) ) }
207219 </ div >
220+ < div className = "heatmap-body" >
221+ < div className = "heatmap-days" >
222+ < span /> < span > Mon</ span > < span /> < span > Wed</ span > < span /> < span > Fri</ span > < span />
223+ </ div >
224+ < div className = "heatmap-grid" style = { { gridTemplateColumns : `repeat(${ heatmap . weeks . length } , 1fr)` } } >
225+ { heatmap . weeks . map ( ( week , wi ) => (
226+ < div key = { wi } className = "heatmap-week" >
227+ { week . map ( ( cell , di ) => (
228+ < div key = { `${ wi } -${ di } ` }
229+ className = { `heatmap-cell heatmap-cell-${ cell . level } ` }
230+ title = { cell . date ? `${ cell . date } : ${ cell . requests } req · ${ formatTokens ( cell . totalTokens ) } tokens` : "" } />
231+ ) ) }
232+ </ div >
233+ ) ) }
234+ </ div >
235+ </ div >
208236 < div className = "heatmap-legend muted" >
209237 < span > { t ( "usage.heatmap.less" ) } </ span >
210238 { [ 0 , 1 , 2 , 3 , 4 ] . map ( l => < span key = { l } className = { `heatmap-cell heatmap-cell-${ l } ` } /> ) }
0 commit comments