@@ -19,6 +19,11 @@ import { useMoveRecommendations } from './useMoveRecommendations'
1919import { MaiaEngineContext } from 'src/contexts/MaiaEngineContext'
2020import { generateColorSanMapping , calculateBlunderMeter } from './utils'
2121import { StockfishEngineContext } from 'src/contexts/StockfishEngineContext'
22+ import {
23+ collectEngineAnalysisData ,
24+ generateAnalysisCacheKey ,
25+ } from 'src/lib/analysisStorage'
26+ import { storeEngineAnalysis } from 'src/api/analysis/analysis'
2227
2328export interface GameAnalysisProgress {
2429 currentMoveIndex : number
@@ -36,6 +41,7 @@ export interface GameAnalysisConfig {
3641export const useAnalysisController = (
3742 game : AnalyzedGame ,
3843 initialOrientation ?: 'white' | 'black' ,
44+ enableAutoSave = true ,
3945) => {
4046 const defaultOrientation = initialOrientation
4147 ? initialOrientation
@@ -74,6 +80,110 @@ export const useAnalysisController = (
7480 currentNode : null ,
7581 } )
7682
83+ const [ lastSavedCacheKey , setLastSavedCacheKey ] = useState < string > ( '' )
84+ const [ hasUnsavedAnalysis , setHasUnsavedAnalysis ] = useState ( false )
85+ const [ isAutoSaving , setIsAutoSaving ] = useState ( false )
86+ const autoSaveTimerRef = useRef < NodeJS . Timeout | null > ( null )
87+
88+ const saveAnalysisToBackend = useCallback ( async ( ) => {
89+ if (
90+ ! enableAutoSave ||
91+ ! game . id ||
92+ game . type === 'custom-pgn' ||
93+ game . type === 'custom-fen' ||
94+ game . type === 'tournament'
95+ ) {
96+ return
97+ }
98+
99+ // Don't save if there are no unsaved changes
100+ if ( ! hasUnsavedAnalysis ) {
101+ return
102+ }
103+
104+ try {
105+ setIsAutoSaving ( true )
106+ const analysisData = collectEngineAnalysisData ( game . tree )
107+
108+ if ( analysisData . length === 0 ) {
109+ setIsAutoSaving ( false )
110+ return
111+ }
112+
113+ const hasMeaningfulAnalysis = analysisData . some (
114+ ( pos ) => ( pos . stockfish && pos . stockfish . depth >= 12 ) || pos . maia ,
115+ )
116+
117+ if ( ! hasMeaningfulAnalysis ) {
118+ setIsAutoSaving ( false )
119+ return
120+ }
121+
122+ const cacheKey = generateAnalysisCacheKey ( analysisData )
123+ if ( cacheKey === lastSavedCacheKey ) {
124+ setIsAutoSaving ( false )
125+ return
126+ }
127+
128+ await storeEngineAnalysis ( game . id , analysisData )
129+ setLastSavedCacheKey ( cacheKey )
130+ setHasUnsavedAnalysis ( false ) // Mark as saved
131+ console . log (
132+ 'Analysis saved to backend:' ,
133+ analysisData . length ,
134+ 'positions' ,
135+ )
136+ } catch ( error ) {
137+ console . warn ( 'Failed to save analysis to backend:' , error )
138+ // Don't show error to user as this is background functionality
139+ } finally {
140+ setIsAutoSaving ( false )
141+ }
142+ } , [
143+ enableAutoSave ,
144+ game . id ,
145+ game . type ,
146+ game . tree ,
147+ lastSavedCacheKey ,
148+ hasUnsavedAnalysis ,
149+ ] )
150+
151+ const saveAnalysisToBackendRef = useRef ( saveAnalysisToBackend )
152+ saveAnalysisToBackendRef . current = saveAnalysisToBackend
153+
154+ useEffect ( ( ) => {
155+ setHasUnsavedAnalysis ( false )
156+ setIsAutoSaving ( false )
157+ setLastSavedCacheKey ( '' )
158+ } , [ game . id , game . type ] )
159+
160+ useEffect ( ( ) => {
161+ if ( analysisState > 0 ) {
162+ setHasUnsavedAnalysis ( true )
163+ }
164+ } , [ analysisState ] )
165+
166+ useEffect ( ( ) => {
167+ if ( ! enableAutoSave ) {
168+ return
169+ }
170+
171+ if ( autoSaveTimerRef . current ) {
172+ clearInterval ( autoSaveTimerRef . current )
173+ }
174+
175+ autoSaveTimerRef . current = setInterval ( ( ) => {
176+ saveAnalysisToBackendRef . current ( )
177+ } , 10000 )
178+
179+ return ( ) => {
180+ if ( autoSaveTimerRef . current ) {
181+ clearInterval ( autoSaveTimerRef . current )
182+ }
183+ saveAnalysisToBackendRef . current ( )
184+ }
185+ } , [ game . id , enableAutoSave ] )
186+
77187 // Simple batch analysis functions that reuse existing analysis infrastructure
78188 const startGameAnalysis = useCallback (
79189 async ( targetDepth : number ) => {
@@ -362,6 +472,16 @@ export const useAnalysisController = (
362472 cancelAnalysis : cancelGameAnalysis ,
363473 resetProgress : resetGameAnalysisProgress ,
364474 isEnginesReady : stockfish . isReady ( ) && maia . status === 'ready' ,
475+ saveAnalysis : saveAnalysisToBackend ,
476+ autoSave : {
477+ hasUnsavedChanges : hasUnsavedAnalysis ,
478+ isSaving : isAutoSaving ,
479+ status : isAutoSaving
480+ ? ( 'saving' as const )
481+ : hasUnsavedAnalysis
482+ ? ( 'unsaved' as const )
483+ : ( 'saved' as const ) ,
484+ } ,
365485 } ,
366486 }
367487}
0 commit comments