@@ -78,9 +78,10 @@ const ChartWrapper = ({ data, options, chartId, onRegisterChart, onSyncHover })
7878} ;
7979
8080export default function ChartContainer ( {
81- files,
82- lossRegex,
83- gradNormRegex,
81+ files,
82+ lossRegex,
83+ gradNormRegex,
84+ otherConfigs = [ ] ,
8485 compareMode,
8586 relativeBaseline = 0.002 ,
8687 absoluteBaseline = 0.005 ,
@@ -138,13 +139,35 @@ export default function ChartContainer({
138139 const enabledFiles = files . filter ( file => file . enabled !== false ) ;
139140
140141 return enabledFiles . map ( file => {
141- if ( ! file . content ) return { ...file , lossData : [ ] , gradNormData : [ ] } ;
142+ if ( ! file . content ) return { ...file , lossData : [ ] , gradNormData : [ ] , othersData : { } } ;
142143
143144 const lines = file . content . split ( '\n' ) ;
144145 const lossData = [ ] ;
145146 const gradNormData = [ ] ;
147+ const otherMetricData = { } ;
146148
147149 try {
150+ // 公用关键词匹配函数
151+ const extractByKeyword = ( content , keyword ) => {
152+ const results = [ ] ;
153+ const lines = content . split ( '\n' ) ;
154+ const numberRegex = / [ + - ] ? \d + (?: \. \d + ) ? (?: [ e E ] [ + - ] ? \d + ) ? / ;
155+ lines . forEach ( ( line ) => {
156+ const keywordIndex = line . toLowerCase ( ) . indexOf ( keyword . toLowerCase ( ) ) ;
157+ if ( keywordIndex !== - 1 ) {
158+ const afterKeyword = line . substring ( keywordIndex + keyword . length ) ;
159+ const numberMatch = afterKeyword . match ( numberRegex ) ;
160+ if ( numberMatch ) {
161+ const value = parseFloat ( numberMatch [ 0 ] ) ;
162+ if ( ! isNaN ( value ) ) {
163+ results . push ( value ) ;
164+ }
165+ }
166+ }
167+ } ) ;
168+ return results ;
169+ } ;
170+
148171 // 使用新的配置格式,同时保持向后兼容
149172 let fileLossConfig , fileGradNormConfig ;
150173
@@ -265,6 +288,29 @@ export default function ChartContainer({
265288 }
266289 } ) ;
267290 }
291+
292+ // 处理其他自定义指标
293+ if ( Array . isArray ( file . config ?. others ) ) {
294+ file . config . others . forEach ( metric => {
295+ let values = [ ] ;
296+ if ( metric . mode === 'keyword' ) {
297+ values = extractByKeyword ( file . content , metric . keyword ) ;
298+ } else {
299+ const regexObj = new RegExp ( metric . regex ) ;
300+ lines . forEach ( line => {
301+ regexObj . lastIndex = 0 ;
302+ const match = regexObj . exec ( line ) ;
303+ if ( match && match [ 1 ] ) {
304+ const value = parseFloat ( match [ 1 ] ) ;
305+ if ( ! isNaN ( value ) ) {
306+ values . push ( value ) ;
307+ }
308+ }
309+ } ) ;
310+ }
311+ otherMetricData [ metric . name || metric . keyword ] = values . map ( ( v , i ) => ( { x : i , y : v } ) ) ;
312+ } ) ;
313+ }
268314 } catch ( error ) {
269315 console . error ( 'Regex error:' , error ) ;
270316 }
@@ -291,28 +337,34 @@ export default function ChartContainer({
291337 return slicedData ;
292338 } ;
293339
340+ const reindexData = ( data ) => data . map ( ( point , index ) => ( { x : index , y : point . y } ) ) ;
341+
294342 const filteredLossData = applyRangeFilter ( lossData ) ;
295343 const filteredGradNormData = applyRangeFilter ( gradNormData ) ;
344+ const filteredOthers = { } ;
345+ Object . entries ( otherMetricData ) . forEach ( ( [ key , data ] ) => {
346+ filteredOthers [ key ] = reindexData ( applyRangeFilter ( data ) ) ;
347+ } ) ;
296348
297- // 重新索引数据点
298- const reindexData = ( data ) => data . map ( ( point , index ) => ( { x : index , y : point . y } ) ) ;
299-
300- return {
301- ...file ,
302- lossData : reindexData ( filteredLossData ) ,
303- gradNormData : reindexData ( filteredGradNormData )
349+ return {
350+ ...file ,
351+ lossData : reindexData ( filteredLossData ) ,
352+ gradNormData : reindexData ( filteredGradNormData ) ,
353+ othersData : filteredOthers
304354 } ;
305355 }
306356
307- return { ...file , lossData, gradNormData } ;
357+ return { ...file , lossData, gradNormData, othersData : otherMetricData } ;
308358 } ) ;
309- } , [ files , lossRegex , gradNormRegex ] ) ;
359+ // eslint-disable-next-line react-hooks/exhaustive-deps
360+ } , [ files , lossRegex , gradNormRegex , otherConfigs ] ) ;
310361
311362 useEffect ( ( ) => {
312363 const maxStep = parsedData . reduce ( ( max , file ) => {
313364 const maxLoss = file . lossData . length > 0 ? file . lossData [ file . lossData . length - 1 ] . x : 0 ;
314365 const maxGrad = file . gradNormData . length > 0 ? file . gradNormData [ file . gradNormData . length - 1 ] . x : 0 ;
315- return Math . max ( max , maxLoss , maxGrad ) ;
366+ const otherMax = Object . values ( file . othersData || { } ) . reduce ( ( m , data ) => Math . max ( m , data . length > 0 ? data [ data . length - 1 ] . x : 0 ) , 0 ) ;
367+ return Math . max ( max , maxLoss , maxGrad , otherMax ) ;
316368 } , 0 ) ;
317369 onMaxStepChange ( maxStep ) ;
318370 } , [ parsedData , onMaxStepChange ] ) ;
@@ -655,6 +707,14 @@ export default function ChartContainer({
655707 . filter ( file => file . gradNormData && file . gradNormData . length > 0 )
656708 . map ( file => ( { name : file . name , data : file . gradNormData } ) ) ;
657709
710+ const otherMetricKeys = otherConfigs . map ( c => c . name || c . keyword ) ;
711+ const otherDataArrays = { } ;
712+ otherMetricKeys . forEach ( key => {
713+ otherDataArrays [ key ] = parsedData
714+ . filter ( file => file . othersData && file . othersData [ key ] && file . othersData [ key ] . length > 0 )
715+ . map ( file => ( { name : file . name , data : file . othersData [ key ] } ) ) ;
716+ } ) ;
717+
658718 // 计算显示的图表数量来决定布局
659719 const enabledFiles = files . filter ( file => file . enabled !== false ) ;
660720 const showingLossCharts = showLoss && lossDataArray . length > 0 ;
@@ -837,6 +897,25 @@ export default function ChartContainer({
837897 </ div >
838898 </ div >
839899 ) }
900+ { otherMetricKeys . length > 0 && (
901+ < div className = "col-span-full overflow-x-auto" >
902+ < div className = "flex gap-3 w-max" >
903+ { otherMetricKeys . map ( ( key , idx ) => (
904+ < div key = { key } className = "w-96" >
905+ < ResizablePanel title = { key } initialHeight = { 440 } >
906+ < ChartWrapper
907+ chartId = { `other-${ idx } ` }
908+ onRegisterChart = { registerChart }
909+ onSyncHover = { syncHoverToAllCharts }
910+ data = { createChartData ( otherDataArrays [ key ] ) }
911+ options = { chartOptions }
912+ />
913+ </ ResizablePanel >
914+ </ div >
915+ ) ) }
916+ </ div >
917+ </ div >
918+ ) }
840919 </ div >
841920 ) ;
842921}
0 commit comments