@@ -20,6 +20,7 @@ import {
2020 MaiaEvaluation ,
2121 StockfishEvaluation ,
2222 GameNode ,
23+ Termination ,
2324} from 'src/types'
2425import { WindowSizeContext , TreeControllerContext , useTour } from 'src/contexts'
2526import { Loading } from 'src/components'
@@ -57,13 +58,91 @@ import { MAIA_MODELS } from 'src/constants/common'
5758import { applyEngineAnalysisData } from 'src/lib/analysis'
5859
5960const EVAL_BAR_RANGE = 4
61+ const CUSTOM_PGN_RESULT_OVERRIDES_STORAGE_KEY =
62+ 'maia_custom_pgn_result_overrides'
63+ const PGN_RESULT_TOKEN_REGEX = / ( .* ?) (?: \s + ) ( 1 - 0 | 0 - 1 | 1 \/ 2 - 1 \/ 2 | \* ) \s * $ /
6064const DEFAULT_STOCKFISH_EVAL_BAR = {
6165 hasEval : false ,
6266 pawns : 0 ,
6367 displayPawns : 0 ,
6468 label : '--' ,
6569}
6670
71+ const splitTrailingPgnResult = (
72+ pgn : string ,
73+ ) : { pgnWithoutResult : string ; result ?: string } => {
74+ const match = pgn . match ( PGN_RESULT_TOKEN_REGEX )
75+ if ( ! match ) {
76+ return { pgnWithoutResult : pgn }
77+ }
78+
79+ return {
80+ pgnWithoutResult : match [ 1 ] . trim ( ) ,
81+ result : match [ 2 ] ,
82+ }
83+ }
84+
85+ const getCustomPgnResultOverrides = ( ) : Record < string , string > => {
86+ if ( typeof window === 'undefined' ) return { }
87+
88+ try {
89+ const raw = window . localStorage . getItem (
90+ CUSTOM_PGN_RESULT_OVERRIDES_STORAGE_KEY ,
91+ )
92+ if ( ! raw ) return { }
93+ const parsed = JSON . parse ( raw )
94+ return parsed && typeof parsed === 'object' ? parsed : { }
95+ } catch ( error ) {
96+ console . warn ( 'Failed to read custom PGN result overrides:' , error )
97+ return { }
98+ }
99+ }
100+
101+ const setCustomPgnResultOverride = ( gameId : string , result : string ) : void => {
102+ if ( typeof window === 'undefined' ) return
103+
104+ try {
105+ const overrides = getCustomPgnResultOverrides ( )
106+ overrides [ gameId ] = result
107+ window . localStorage . setItem (
108+ CUSTOM_PGN_RESULT_OVERRIDES_STORAGE_KEY ,
109+ JSON . stringify ( overrides ) ,
110+ )
111+ } catch ( error ) {
112+ console . warn ( 'Failed to store custom PGN result override:' , error )
113+ }
114+ }
115+
116+ const getCustomPgnResultOverride = ( gameId : string ) : string | undefined => {
117+ const override = getCustomPgnResultOverrides ( ) [ gameId ]
118+ return typeof override === 'string' ? override : undefined
119+ }
120+
121+ const resultTokenToWinner = (
122+ result : string ,
123+ ) : Termination [ 'winner' ] | undefined => {
124+ if ( result === '1-0' ) return 'white'
125+ if ( result === '0-1' ) return 'black'
126+ if ( result === '1/2-1/2' ) return 'none'
127+ return undefined
128+ }
129+
130+ const applyCustomResultOverride = (
131+ game : AnalyzedGame ,
132+ result ?: string ,
133+ ) : AnalyzedGame => {
134+ if ( ! result || result === '*' ) return game
135+
136+ return {
137+ ...game ,
138+ termination : {
139+ ...( game . termination || { } ) ,
140+ result,
141+ winner : resultTokenToWinner ( result ) ,
142+ } ,
143+ }
144+ }
145+
67146const AnalysisPage : NextPage = ( ) => {
68147 const { startTour, tourState } = useTour ( )
69148
@@ -162,12 +241,16 @@ const AnalysisPage: NextPage = () => {
162241 updateUrl = true ,
163242 ) => {
164243 const game = await fetchAnalyzedMaiaGame ( id , type )
244+ const gameWithOverrides =
245+ type === 'custom'
246+ ? applyCustomResultOverride ( game , getCustomPgnResultOverride ( id ) )
247+ : game
165248
166249 if ( setCurrentMove ) setCurrentMove ( 0 )
167250
168- setAnalyzedGame ( { ...game , type } )
251+ setAnalyzedGame ( { ...gameWithOverrides , type } )
169252 setCurrentId ( [ id , type ] )
170- await loadGameAnalysisCache ( { ...game , type } )
253+ await loadGameAnalysisCache ( { ...gameWithOverrides , type } )
171254
172255 if ( updateUrl ) {
173256 router . push ( `/analysis/${ id } /${ type } ` , undefined , {
@@ -347,14 +430,30 @@ const Analysis: React.FC<Props> = ({
347430 const handleCustomAnalysis = useCallback (
348431 ( type : 'fen' | 'pgn' , data : string , name ?: string ) => {
349432 ; ( async ( ) => {
350- const { game_id } = await storeCustomGame ( {
351- name : name ,
352- pgn : type === 'pgn' ? data : undefined ,
353- fen : type === 'fen' ? data : undefined ,
354- } )
433+ try {
434+ const pgnPayload =
435+ type === 'pgn' ? splitTrailingPgnResult ( data ) : undefined
436+ const { game_id } = await storeCustomGame ( {
437+ name : name ,
438+ pgn :
439+ type === 'pgn' ? pgnPayload ?. pgnWithoutResult || data : undefined ,
440+ fen : type === 'fen' ? data : undefined ,
441+ } )
442+
443+ if ( pgnPayload ?. result && pgnPayload . result !== '*' ) {
444+ setCustomPgnResultOverride ( game_id , pgnPayload . result )
445+ }
355446
356- setShowCustomModal ( false )
357- router . push ( `/analysis/${ game_id } /custom` )
447+ setShowCustomModal ( false )
448+ router . push ( `/analysis/${ game_id } /custom` )
449+ } catch ( error ) {
450+ const message =
451+ error instanceof Error
452+ ? error . message
453+ : 'Failed to store custom game'
454+ console . error ( 'Custom analysis import failed:' , error )
455+ toast . error ( message )
456+ }
358457 } ) ( )
359458 } ,
360459 [ ] ,
0 commit comments