@@ -17,6 +17,17 @@ const formatCost = (cost) => {
1717
1818const formatPercent = ( value ) => `${ Number ( value || 0 ) . toFixed ( 1 ) } %`
1919
20+ const formatTpr = ( value ) => {
21+ if ( value === undefined || value === null || Number . isNaN ( value ) ) return '0.0'
22+ return Number ( value ) . toLocaleString ( 'en-US' , { minimumFractionDigits : 1 , maximumFractionDigits : 1 } )
23+ }
24+
25+ const formatTprAxis = ( value ) => {
26+ if ( value >= 1_000_000 ) return `${ ( value / 1_000_000 ) . toFixed ( 1 ) } M`
27+ if ( value >= 1_000 ) return `${ ( value / 1_000 ) . toFixed ( 1 ) } K`
28+ return Math . round ( value ) . toLocaleString ( 'en-US' )
29+ }
30+
2031const getStatus = ( status ) => status === 'failure' ? 'failure' : 'success'
2132const getSkillLabel = ( value ) => value || 'Unknown'
2233const getProjectLabel = ( value ) => value || 'Unknown Project'
@@ -468,8 +479,18 @@ function SkillsPanel({ skillRuns = [], skillDailyStats = [], dateRange, customRa
468479 }
469480 } , [ activeSkillName , rangeBoundaries ] )
470481 const detailTrendSeries = trendTime === 'hour' ? detailHourlySeries : detailDailySeries
471- const detailHasTokenSignal = detailTrendSeries . some ( p => ( p . input_tokens || 0 ) > 0 || ( p . output_tokens || 0 ) > 0 )
472- const detailUseRunFallbackSeries = detailTrendSeries . length > 0 && ! detailHasTokenSignal
482+ const detailTprSeries = useMemo ( ( ) => {
483+ return detailTrendSeries . map ( point => {
484+ const totalTokens = ( point . input_tokens || 0 ) + ( point . output_tokens || 0 )
485+ const runCount = point . run_count || 0
486+ const tpr = runCount > 0 ? totalTokens / runCount : 0
487+ return {
488+ ...point ,
489+ total_tokens : totalTokens ,
490+ tpr,
491+ }
492+ } )
493+ } , [ detailTrendSeries ] )
473494
474495 const detailProjectRows = useMemo ( ( ) => aggregateDimensionRows ( detailRuns , 'project' ) , [ detailRuns ] )
475496 const detailDeviceRows = useMemo ( ( ) => aggregateDimensionRows ( detailRuns , 'machine' ) , [ detailRuns ] )
@@ -814,47 +835,54 @@ function SkillsPanel({ skillRuns = [], skillDailyStats = [], dateRange, customRa
814835
815836 < div className = "chart-card chart-full" >
816837 < div className = "chart-header" >
817- < h3 > Token Trend</ h3 >
838+ < h3 > TPR Trend (Tokens per Run) </ h3 >
818839 </ div >
819840 < div className = "chart-body chart-body-dark" >
820- { detailTrendSeries . length > 0 ? (
841+ { detailTprSeries . length > 0 ? (
821842 < ResponsiveContainer width = "100%" height = { 260 } >
822- < AreaChart data = { detailTrendSeries } margin = { { top : 10 , right : 10 , left : 0 , bottom : 0 } } >
843+ < AreaChart data = { detailTprSeries } margin = { { top : 10 , right : 10 , left : 0 , bottom : 0 } } >
823844 < defs >
824- < linearGradient id = "gradSkillDetailTokens " x1 = "0" y1 = "0" x2 = "0" y2 = "1" >
825- < stop offset = "0%" stopColor = "#3b82f6 " stopOpacity = { 0.4 } />
826- < stop offset = "100%" stopColor = "#3b82f6 " stopOpacity = { 0 } />
845+ < linearGradient id = "gradSkillDetailTpr " x1 = "0" y1 = "0" x2 = "0" y2 = "1" >
846+ < stop offset = "0%" stopColor = "#22c55e " stopOpacity = { 0.4 } />
847+ < stop offset = "100%" stopColor = "#22c55e " stopOpacity = { 0 } />
827848 </ linearGradient >
828849 </ defs >
829850 < CartesianGrid strokeDasharray = "4 4" stroke = { isDarkMode ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)' } />
830851 < XAxis dataKey = "label" stroke = { isDarkMode ? '#6e7681' : '#57606a' } tick = { CHART_TYPOGRAPHY . axisTick } axisLine = { false } tickLine = { false } />
831- < YAxis stroke = { isDarkMode ? '#6e7681' : '#57606a' } tick = { CHART_TYPOGRAPHY . axisTick } axisLine = { false } tickLine = { false } tickFormatter = { ( v ) => v >= 1_000_000 ? `${ ( v / 1_000_000 ) . toFixed ( 1 ) } M` : v . toLocaleString ( ) } />
852+ < YAxis
853+ stroke = { isDarkMode ? '#6e7681' : '#57606a' }
854+ tick = { CHART_TYPOGRAPHY . axisTick }
855+ axisLine = { false }
856+ tickLine = { false }
857+ tickFormatter = { formatTprAxis }
858+ />
832859 < Tooltip content = { ( { active, payload, label } ) => {
833860 if ( ! active || ! payload ?. length ) return null
834861 const item = payload [ 0 ] . payload
835862 return (
836863 < div style = { { padding : '8px 10px' , background : isDarkMode ? 'rgba(15,23,42,0.95)' : 'white' , border : `1px solid ${ isDarkMode ? 'rgba(255,255,255,0.08)' : '#e2e8f0' } ` , borderRadius : 8 } } >
837864 < div style = { { ...CHART_TYPOGRAPHY . tooltipLabel , marginBottom : 4 } } > { label } </ div >
838- < div style = { CHART_TYPOGRAPHY . tooltipItem } > Attempts: { ( item . run_count || 0 ) . toLocaleString ( ) } </ div >
839- < div style = { CHART_TYPOGRAPHY . tooltipItem } > Input: { formatNumber ( item . input_tokens ) } </ div >
840- < div style = { CHART_TYPOGRAPHY . tooltipItem } > Output: { formatNumber ( item . output_tokens ) } </ div >
841- { detailUseRunFallbackSeries && < div style = { CHART_TYPOGRAPHY . tooltipItem } > Runs: { formatNumber ( item . run_count ) } </ div > }
865+ < div style = { { ...CHART_TYPOGRAPHY . tooltipItem , color : '#22c55e' } } > TPR: { formatTpr ( item . tpr ) } tokens/run</ div >
866+ < div style = { CHART_TYPOGRAPHY . tooltipItem } > Runs: { formatNumber ( item . run_count || 0 ) } </ div >
867+ < div style = { CHART_TYPOGRAPHY . tooltipItem } > Total tokens: { formatNumber ( item . total_tokens || 0 ) } </ div >
868+ < div style = { CHART_TYPOGRAPHY . tooltipItem } > Input: { formatNumber ( item . input_tokens || 0 ) } </ div >
869+ < div style = { CHART_TYPOGRAPHY . tooltipItem } > Output: { formatNumber ( item . output_tokens || 0 ) } </ div >
842870 < div style = { { ...CHART_TYPOGRAPHY . tooltipItem , color : '#10b981' } } > Cost: { formatCost ( item . estimated_cost ) } </ div >
843871 </ div >
844872 )
845873 } } />
846- { detailUseRunFallbackSeries ? (
847- < Area type = "monotone" dataKey = "run_count" name = "Runs" stroke = "#f59e0b" fillOpacity = { 0.25 } fill = "#f59e0b" strokeWidth = { 2 } />
848- ) : (
849- < >
850- < Area type = "monotone" dataKey = "input_tokens" name = "Input" stroke = "#3b82f6" fill = "url(#gradSkillDetailTokens)" strokeWidth = { 2 } />
851- < Area type = "monotone" dataKey = "output_tokens" name = "Output" stroke = "#8b5cf6" fillOpacity = { 0.2 } fill = "#8b5cf6" strokeWidth = { 2 } />
852- </ >
853- ) }
874+ < Area
875+ type = "monotone"
876+ dataKey = "tpr"
877+ name = "TPR"
878+ stroke = "#22c55e"
879+ fill = "url(#gradSkillDetailTpr)"
880+ strokeWidth = { 2 }
881+ />
854882 </ AreaChart >
855883 </ ResponsiveContainer >
856884 ) : (
857- < div className = "empty-state" > No trend data for this skill</ div >
885+ < div className = "empty-state" > No TPR trend data for this skill</ div >
858886 ) }
859887 </ div >
860888 </ div >
0 commit comments