@@ -123,6 +123,8 @@ function canonicalizePath(p: string): string {
123123
124124function normDir ( p ?: string ) : string { return canonicalizePath ( getDir ( p ) ) ; }
125125
126+ function historyDirKey ( p ?: string ) : string { return normDir ( p ) || '__unknown__' ; }
127+
126128// ---------- Helpers ----------
127129
128130const UUID_REGEX = / ^ [ 0 - 9 a - f A - F ] { 8 } - [ 0 - 9 a - f A - F ] { 4 } - [ 0 - 9 a - f A - F ] { 4 } - [ 0 - 9 a - f A - F ] { 4 } - [ 0 - 9 a - f A - F ] { 12 } $ / ;
@@ -869,13 +871,13 @@ export default function CodexFlowManagerUI() {
869871 // 若当前选择无效或为空,重置为缓存中的第一组(除非是点击项目触发的切换)
870872 if ( ! skipAuto ) {
871873 const ids = new Set ( cached . map ( ( x ) => x . id ) ) ;
872- const dirs = new Set ( cached . map ( ( x ) => normDir ( x . filePath ) || '__unknown__' ) ) ;
874+ const dirs = new Set ( cached . map ( ( x ) => historyDirKey ( x . filePath ) ) ) ;
873875 if ( ! selectedHistoryId || ! ids . has ( selectedHistoryId ) || ! selectedHistoryDir || ! dirs . has ( selectedHistoryDir ) ) {
874- const firstKey = normDir ( cached [ 0 ] ?. filePath || '' ) || '__unknown__' ;
876+ const firstKey = historyDirKey ( cached [ 0 ] ?. filePath ) ;
875877 if ( firstKey ) {
876878 setSelectedHistoryDir ( firstKey ) ;
877879 const firstInDir = cached
878- . filter ( ( x ) => ( normDir ( x . filePath ) || '__unknown__' ) === firstKey )
880+ . filter ( ( x ) => historyDirKey ( x . filePath ) === firstKey )
879881 . sort ( ( a , b ) => new Date ( b . date ) . getTime ( ) - new Date ( a . date ) . getTime ( ) ) [ 0 ] ;
880882 setSelectedHistoryId ( firstInDir ?. id || null ) ;
881883 setCenterMode ( 'history' ) ;
@@ -917,15 +919,15 @@ export default function CodexFlowManagerUI() {
917919 // 校验并修正当前选择,避免缓存残留导致的空白详情
918920 if ( ! skipAuto ) {
919921 const ids = new Set ( mapped . map ( ( x ) => x . id ) ) ;
920- const dirs = new Set ( mapped . map ( ( x ) => normDir ( x . filePath ) ) . filter ( Boolean ) ) ;
922+ const dirs = new Set ( mapped . map ( ( x ) => historyDirKey ( x . filePath ) ) ) ;
921923 const needResetId = ! selectedHistoryId || ! ids . has ( selectedHistoryId ) ;
922924 const needResetDir = ! selectedHistoryDir || ! dirs . has ( selectedHistoryDir ) ;
923925 if ( needResetId || needResetDir ) {
924- const firstKey = normDir ( mapped [ 0 ] ?. filePath || '' ) || '__unknown__' ;
926+ const firstKey = historyDirKey ( mapped [ 0 ] ?. filePath ) ;
925927 if ( firstKey ) {
926928 setSelectedHistoryDir ( firstKey ) ;
927929 const firstInDir = mapped
928- . filter ( ( x ) => ( normDir ( x . filePath ) || '__unknown__' ) === firstKey )
930+ . filter ( ( x ) => historyDirKey ( x . filePath ) === firstKey )
929931 . sort ( ( a , b ) => new Date ( b . date ) . getTime ( ) - new Date ( a . date ) . getTime ( ) ) [ 0 ] ;
930932 setSelectedHistoryId ( firstInDir ?. id || null ) ;
931933 setCenterMode ( 'history' ) ;
@@ -1039,18 +1041,18 @@ export default function CodexFlowManagerUI() {
10391041 } catch { }
10401042 // 若当前选中项被移除,选择同组最新一条
10411043 if ( selectedHistoryId && ! next . some ( ( x ) => x . id === selectedHistoryId ) ) {
1042- const key = normDir ( filePath ) || '__unknown__' ;
1044+ const key = historyDirKey ( filePath ) ;
10431045 const restInGroup = next
1044- . filter ( ( x ) => ( normDir ( x . filePath ) || '__unknown__' ) === key )
1046+ . filter ( ( x ) => historyDirKey ( x . filePath ) === key )
10451047 . sort ( ( a , b ) => new Date ( b . date ) . getTime ( ) - new Date ( a . date ) . getTime ( ) ) ;
10461048 if ( restInGroup . length > 0 ) setSelectedHistoryId ( restInGroup [ 0 ] . id ) ;
10471049 else {
1048- const groups = Array . from ( new Set ( next . map ( ( x ) => normDir ( x . filePath ) || '__unknown__' ) ) ) ;
1050+ const groups = Array . from ( new Set ( next . map ( ( x ) => historyDirKey ( x . filePath ) ) ) ) ;
10491051 const firstKey = groups [ 0 ] || null ;
10501052 setSelectedHistoryDir ( firstKey ) ;
10511053 if ( firstKey ) {
10521054 const firstInDir = next
1053- . filter ( ( x ) => ( normDir ( x . filePath ) || '__unknown__' ) === firstKey )
1055+ . filter ( ( x ) => historyDirKey ( x . filePath ) === firstKey )
10541056 . sort ( ( a , b ) => new Date ( b . date ) . getTime ( ) - new Date ( a . date ) . getTime ( ) ) [ 0 ] ;
10551057 setSelectedHistoryId ( firstInDir ? firstInDir . id : null ) ;
10561058 } else {
@@ -2380,51 +2382,84 @@ function HistoryDetail({ sessions, selectedHistoryId, onBack, onResume, onResume
23802382 const [ localSessions , setLocalSessions ] = useState < HistorySession [ ] > ( sessions ) ;
23812383 const [ typeFilter , setTypeFilter ] = useState < Record < string , boolean > > ( { } ) ;
23822384 const reqSeq = useRef ( 0 ) ;
2383-
2384- useEffect ( ( ) => setLocalSessions ( sessions ) , [ sessions ] ) ;
2385+ const lastLoadedFingerprintRef = useRef < string > ( "" ) ;
2386+ const selectedSession = useMemo ( ( ) => sessions . find ( ( x ) => x . id === selectedHistoryId ) || null , [ sessions , selectedHistoryId ] ) ;
2387+ const selectedLocalSession = useMemo (
2388+ ( ) => localSessions . find ( ( x ) => x . id === selectedHistoryId ) || null ,
2389+ [ localSessions , selectedHistoryId ] ,
2390+ ) ;
2391+ const selectedSessionFingerprint = useMemo ( ( ) => {
2392+ if ( ! selectedSession ) return "none" ;
2393+ return [
2394+ selectedSession . id ,
2395+ selectedSession . filePath || "" ,
2396+ selectedSession . date || "" ,
2397+ selectedSession . resumeMode || "" ,
2398+ selectedSession . resumeId || "" ,
2399+ selectedSession . preview || "" ,
2400+ selectedSession . rawDate || "" ,
2401+ ] . join ( "|" ) ;
2402+ } , [ selectedSession ] ) ;
2403+
2404+ // 刷新列表时保留已加载的消息内容,避免详情面板闪烁
2405+ useEffect ( ( ) => {
2406+ setLocalSessions ( ( cur ) => {
2407+ const prevMap = new Map ( cur . map ( ( x ) => [ x . id , x ] ) ) ;
2408+ return sessions . map ( ( s ) => {
2409+ const prev = prevMap . get ( s . id ) ;
2410+ if ( ! prev ) return s ;
2411+ const prevMsgs = Array . isArray ( prev . messages ) ? prev . messages : [ ] ;
2412+ const nextMsgs = Array . isArray ( s . messages ) ? s . messages : [ ] ;
2413+ if ( nextMsgs . length === 0 && prevMsgs . length > 0 ) return { ...s , messages : prevMsgs } ;
2414+ return s ;
2415+ } ) ;
2416+ } ) ;
2417+ } , [ sessions ] ) ;
23852418
23862419 useEffect ( ( ) => {
2420+ if ( ! selectedHistoryId || ! selectedSession || ! selectedSession . filePath ) return ;
2421+ const signature = selectedSessionFingerprint ;
2422+ const hasMessages = ! ! ( selectedLocalSession && Array . isArray ( selectedLocalSession . messages ) && selectedLocalSession . messages . length > 0 ) ;
2423+ if ( hasMessages && lastLoadedFingerprintRef . current === signature ) return ;
2424+ setLoaded ( false ) ;
2425+ const seq = ++ reqSeq . current ;
23872426 ( async ( ) => {
2388- if ( ! selectedHistoryId ) return ;
2389- const s = sessions . find ( ( x ) => x . id === selectedHistoryId ) ;
2390- if ( ! s || ! s . filePath ) return ;
2391- setLoaded ( false ) ;
2392- const seq = ++ reqSeq . current ;
23932427 try {
2394- const res : any = await window . host . history . read ( { filePath : s . filePath } ) ;
2428+ const res : any = await window . host . history . read ( { filePath : selectedSession . filePath } ) ;
23952429 const msgs = ( res . messages || [ ] ) . map ( ( m : any ) => ( { role : m . role as any , content : m . content } ) ) ;
23962430 if ( seq === reqSeq . current ) {
23972431 setLocalSessions ( ( cur ) => cur . map ( ( x ) => ( x . id === selectedHistoryId ? { ...x , messages : msgs } : x ) ) ) ;
23982432 setSkipped ( res . skippedLines || 0 ) ;
23992433 setLoaded ( true ) ;
2434+ lastLoadedFingerprintRef . current = signature ;
24002435 }
2401- try {
2402- const allKeys = new Set < string > ( ) ;
2403- for ( const mm of msgs ) {
2404- for ( const it of ( mm . content || [ ] ) ) for ( const k of keysOfItemCanonical ( it ) ) allKeys . add ( k ) ;
2405- }
2406- // 去重策略:若同时存在 base 与 message.<base>,则仅保留 base(避免重复展示)。
2407- const BASES = new Set ( [ 'input_text' , 'output_text' , 'text' , 'code' , 'json' , 'instructions' , 'environment_context' , 'summary' ] ) ;
2408- const hasBase = ( b : string ) => allKeys . has ( b ) ;
2409- const filtered : string [ ] = [ ] ;
2410- for ( const k of Array . from ( allKeys ) ) {
2411- if ( k . startsWith ( 'message.' ) ) {
2412- const tail = k . slice ( 'message.' . length ) ;
2413- if ( BASES . has ( tail ) && hasBase ( tail ) ) continue ; // drop redundant message.<base>
2436+ try {
2437+ const allKeys = new Set < string > ( ) ;
2438+ for ( const mm of msgs ) {
2439+ for ( const it of ( mm . content || [ ] ) ) for ( const k of keysOfItemCanonical ( it ) ) allKeys . add ( k ) ;
24142440 }
2415- filtered . push ( k ) ;
2416- }
2417- filtered . sort ( ) ;
2418- const next : Record < string , boolean > = { } ;
2419- for ( const k of filtered ) next [ k ] = ( k === 'input_text' || k === 'output_text' ) ;
2420- if ( seq === reqSeq . current ) setTypeFilter ( next ) ;
2421- } catch { }
2441+ // 去重策略:若同时存在 base 与 message.<base>,则仅保留 base(避免重复展示)。
2442+ const BASES = new Set ( [ 'input_text' , 'output_text' , 'text' , 'code' , 'json' , 'instructions' , 'environment_context' , 'summary' ] ) ;
2443+ const hasBase = ( b : string ) => allKeys . has ( b ) ;
2444+ const filtered : string [ ] = [ ] ;
2445+ for ( const k of Array . from ( allKeys ) ) {
2446+ if ( k . startsWith ( 'message.' ) ) {
2447+ const tail = k . slice ( 'message.' . length ) ;
2448+ if ( BASES . has ( tail ) && hasBase ( tail ) ) continue ;
2449+ }
2450+ filtered . push ( k ) ;
2451+ }
2452+ filtered . sort ( ) ;
2453+ const next : Record < string , boolean > = { } ;
2454+ for ( const k of filtered ) next [ k ] = ( k === 'input_text' || k === 'output_text' ) ;
2455+ if ( seq === reqSeq . current ) setTypeFilter ( next ) ;
2456+ } catch { }
24222457 } catch ( e ) {
24232458 console . warn ( 'history.read failed' , e ) ;
24242459 if ( seq === reqSeq . current ) setLoaded ( true ) ;
24252460 }
24262461 } ) ( ) ;
2427- } , [ selectedHistoryId ] ) ;
2462+ } , [ selectedHistoryId , selectedSession , selectedSessionFingerprint , selectedLocalSession ] ) ;
24282463
24292464 function buildFilteredText ( ) : string {
24302465 if ( ! selectedHistoryId ) return '' ;
0 commit comments