1- import React , { useState , useCallback , useEffect } from 'react' ;
1+ import React , { useState , useCallback , useEffect , useRef } from 'react' ;
22import { FileUpload } from './components/FileUpload' ;
33import { RegexControls } from './components/RegexControls' ;
44import { FileList } from './components/FileList' ;
@@ -9,27 +9,36 @@ import { FileConfigModal } from './components/FileConfigModal';
99import { PanelLeftClose , PanelLeftOpen } from 'lucide-react' ;
1010import { mergeFilesWithReplacement } from './utils/mergeFiles.js' ;
1111
12+ // 默认全局解析配置
13+ export const DEFAULT_GLOBAL_PARSING_CONFIG = {
14+ metrics : [
15+ {
16+ name : 'Loss' ,
17+ mode : 'keyword' ,
18+ keyword : 'loss:' ,
19+ regex : 'loss:\\s*([\\d.eE+-]+)'
20+ } ,
21+ {
22+ name : 'Grad Norm' ,
23+ mode : 'keyword' ,
24+ keyword : 'norm:' ,
25+ regex : 'grad[\\s_]norm:\\s*([\\d.eE+-]+)'
26+ }
27+ ] ,
28+ useStepKeyword : false ,
29+ stepKeyword : 'step:'
30+ } ;
31+
1232function App ( ) {
13- const [ uploadedFiles , setUploadedFiles ] = useState ( [ ] ) ;
14-
33+ const [ uploadedFiles , setUploadedFiles ] = useState ( ( ) => {
34+ const stored = localStorage . getItem ( 'uploadedFiles' ) ;
35+ return stored ? JSON . parse ( stored ) : [ ] ;
36+ } ) ;
37+
1538 // 全局解析配置状态
16- const [ globalParsingConfig , setGlobalParsingConfig ] = useState ( {
17- metrics : [
18- {
19- name : 'Loss' ,
20- mode : 'keyword' , // 'keyword' | 'regex'
21- keyword : 'loss:' ,
22- regex : 'loss:\\s*([\\d.eE+-]+)'
23- } ,
24- {
25- name : 'Grad Norm' ,
26- mode : 'keyword' ,
27- keyword : 'norm:' ,
28- regex : 'grad[\\s_]norm:\\s*([\\d.eE+-]+)'
29- }
30- ] ,
31- useStepKeyword : false ,
32- stepKeyword : 'step:'
39+ const [ globalParsingConfig , setGlobalParsingConfig ] = useState ( ( ) => {
40+ const stored = localStorage . getItem ( 'globalParsingConfig' ) ;
41+ return stored ? JSON . parse ( stored ) : JSON . parse ( JSON . stringify ( DEFAULT_GLOBAL_PARSING_CONFIG ) ) ;
3342 } ) ;
3443
3544 const [ compareMode , setCompareMode ] = useState ( 'normal' ) ;
@@ -42,6 +51,29 @@ function App() {
4251 const [ xRange , setXRange ] = useState ( { min : undefined , max : undefined } ) ;
4352 const [ maxStep , setMaxStep ] = useState ( 0 ) ;
4453 const [ sidebarVisible , setSidebarVisible ] = useState ( true ) ;
54+ const savingDisabledRef = useRef ( false ) ;
55+
56+ // 持久化配置到 localStorage
57+ useEffect ( ( ) => {
58+ if ( savingDisabledRef . current ) return ;
59+ localStorage . setItem ( 'globalParsingConfig' , JSON . stringify ( globalParsingConfig ) ) ;
60+ } , [ globalParsingConfig ] ) ;
61+
62+ useEffect ( ( ) => {
63+ if ( savingDisabledRef . current ) return ;
64+ const serialized = uploadedFiles . map ( ( { id, name, enabled, content, config } ) => ( {
65+ id,
66+ name,
67+ enabled,
68+ content,
69+ config
70+ } ) ) ;
71+ if ( serialized . length > 0 ) {
72+ localStorage . setItem ( 'uploadedFiles' , JSON . stringify ( serialized ) ) ;
73+ } else {
74+ localStorage . removeItem ( 'uploadedFiles' ) ;
75+ }
76+ } , [ uploadedFiles ] ) ;
4577
4678 const handleFilesUploaded = useCallback ( ( files ) => {
4779 const filesWithDefaults = files . map ( file => ( {
@@ -132,7 +164,19 @@ function App() {
132164 useStepKeyword : newConfig . useStepKeyword ,
133165 stepKeyword : newConfig . stepKeyword
134166 }
135- } ) ) ) ;
167+ } ) ) ) ;
168+ } , [ ] ) ;
169+
170+ // 重置配置
171+ const handleResetConfig = useCallback ( ( ) => {
172+ savingDisabledRef . current = true ;
173+ localStorage . removeItem ( 'globalParsingConfig' ) ;
174+ localStorage . removeItem ( 'uploadedFiles' ) ;
175+ setGlobalParsingConfig ( JSON . parse ( JSON . stringify ( DEFAULT_GLOBAL_PARSING_CONFIG ) ) ) ;
176+ setUploadedFiles ( [ ] ) ;
177+ setTimeout ( ( ) => {
178+ savingDisabledRef . current = false ;
179+ } , 0 ) ;
136180 } , [ ] ) ;
137181
138182 // 全局拖拽事件处理
@@ -306,9 +350,16 @@ function App() {
306350 </ svg >
307351 < span > GitHub</ span >
308352 </ a >
353+ < button
354+ onClick = { handleResetConfig }
355+ className = "inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium bg-red-100 text-red-700 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2"
356+ aria-label = "重置配置"
357+ >
358+ 重置配置
359+ </ button >
309360 </ div >
310361 </ div >
311-
362+
312363 < FileUpload onFilesUploaded = { handleFilesUploaded } />
313364
314365 < RegexControls
0 commit comments