1- import React from 'react'
1+ import React , { useMemo , useCallback , useState , useEffect , useRef } from 'react'
22import { Highlight , MoveMap , BlunderMeter } from '../Analysis'
3- import { useAnalysisController } from 'src/hooks'
4- import { AnalyzedGame } from 'src/types'
3+ import { GameNode } from 'src/types'
54import { GameTree } from 'src/types/base/tree'
6- import { Chess } from 'chess.ts'
7-
8- // Create a mock analyzed game for when no real game is available
9- const createMockAnalyzedGame = ( ) : AnalyzedGame => ( {
10- id : 'mock' ,
11- blackPlayer : { name : 'Player' , rating : undefined } ,
12- whitePlayer : { name : 'Player' , rating : undefined } ,
13- moves : [ ] ,
14- availableMoves : [ ] ,
15- gameType : 'play' ,
16- termination : {
17- result : '*' ,
18- winner : 'none' ,
19- condition : 'Normal' ,
20- } ,
21- maiaEvaluations : [ ] ,
22- stockfishEvaluations : [ ] ,
23- tree : new GameTree ( new Chess ( ) . fen ( ) ) ,
24- type : 'play' ,
25- } )
5+ import { Chess , PieceSymbol } from 'chess.ts'
6+ import type { Key } from 'chessground/types'
7+ import type { DrawShape } from 'chessground/draw'
8+ import toast from 'react-hot-toast'
269
2710interface Props {
28- analyzedGame : AnalyzedGame | null
11+ currentNode : GameNode | null
12+ gameTree : GameTree | null
2913 analysisEnabled : boolean
3014 onToggleAnalysis : ( ) => void
15+ playerColor : 'white' | 'black'
16+ maiaVersion : string
17+ analysisController : any // Analysis controller passed from parent
3118}
3219
3320export const OpeningDrillAnalysis : React . FC < Props > = ( {
34- analyzedGame,
21+ currentNode,
22+ gameTree,
3523 analysisEnabled,
3624 onToggleAnalysis,
25+ playerColor,
26+ maiaVersion,
27+ analysisController,
3728} ) => {
38- // Always call the hook but use mock data when disabled
39- const analysisController = useAnalysisController (
40- analyzedGame || createMockAnalyzedGame ( ) ,
41- 'white' ,
29+ const [ hoverArrow , setHoverArrow ] = useState < DrawShape | null > ( null )
30+ const toastId = useRef < string | null > ( null )
31+
32+ // Toast notifications for Maia model status
33+ useEffect ( ( ) => {
34+ return ( ) => {
35+ toast . dismiss ( )
36+ }
37+ } , [ ] )
38+
39+ useEffect ( ( ) => {
40+ if ( analysisController . maiaStatus === 'loading' && ! toastId . current ) {
41+ toastId . current = toast . loading ( 'Loading Maia Model...' )
42+ } else if ( analysisController . maiaStatus === 'ready' ) {
43+ if ( toastId . current ) {
44+ toast . success ( 'Loaded Maia! Analysis is ready' , {
45+ id : toastId . current ,
46+ } )
47+ toastId . current = null
48+ } else {
49+ toast . success ( 'Loaded Maia! Analysis is ready' )
50+ }
51+ }
52+ } , [ analysisController . maiaStatus ] )
53+
54+ const hover = useCallback (
55+ ( move ?: string ) => {
56+ if ( move && analysisEnabled ) {
57+ setHoverArrow ( {
58+ orig : move . slice ( 0 , 2 ) as Key ,
59+ dest : move . slice ( 2 , 4 ) as Key ,
60+ brush : 'green' ,
61+ modifiers : { lineWidth : 10 } ,
62+ } )
63+ } else {
64+ setHoverArrow ( null )
65+ }
66+ } ,
67+ [ analysisEnabled ] ,
68+ )
69+
70+ const makeMove = useCallback (
71+ ( move : string ) => {
72+ if ( ! analysisEnabled || ! currentNode || ! gameTree ) return
73+
74+ const chess = new Chess ( currentNode . fen )
75+ const moveAttempt = chess . move ( {
76+ from : move . slice ( 0 , 2 ) ,
77+ to : move . slice ( 2 , 4 ) ,
78+ promotion : move [ 4 ] ? ( move [ 4 ] as PieceSymbol ) : undefined ,
79+ } )
80+
81+ if ( moveAttempt ) {
82+ const newFen = chess . fen ( )
83+ const moveString =
84+ moveAttempt . from +
85+ moveAttempt . to +
86+ ( moveAttempt . promotion ? moveAttempt . promotion : '' )
87+ const san = moveAttempt . san
88+
89+ // Check if this move already exists as a child
90+ const existingChild = currentNode . children . find (
91+ ( child : any ) => child . move === moveString ,
92+ )
93+
94+ if ( existingChild ) {
95+ // Move already exists, just navigate to it
96+ analysisController . goToNode ( existingChild )
97+ } else if ( currentNode . mainChild ?. move === moveString ) {
98+ // This is the main child move
99+ analysisController . goToNode ( currentNode . mainChild )
100+ } else {
101+ // Create new variation
102+ const newVariation = gameTree . addVariation (
103+ currentNode ,
104+ newFen ,
105+ moveString ,
106+ san ,
107+ analysisController . currentMaiaModel ,
108+ )
109+ analysisController . goToNode ( newVariation )
110+ }
111+ }
112+ } ,
113+ [ analysisEnabled , currentNode , gameTree , analysisController ] ,
42114 )
43- const mockHover = ( ) => {
44- // No-op for disabled analysis
45- }
46115
47- const mockMakeMove = ( ) => {
48- // No-op for disabled analysis
49- }
116+ // No-op handlers for blurred analysis components when disabled
117+ const mockHover = useCallback ( ( ) => {
118+ // Intentionally empty - no interaction allowed when analysis disabled
119+ } , [ ] )
120+
121+ const mockMakeMove = useCallback ( ( ) => {
122+ // Intentionally empty - no moves allowed when analysis disabled
123+ } , [ ] )
124+
125+ const mockSetHoverArrow = useCallback ( ( ) => {
126+ // Intentionally empty - no hover arrows when analysis disabled
127+ } , [ ] )
128+
129+ // Create empty data structures that match expected types
130+ const emptyBlunderMeterData = useMemo (
131+ ( ) => ( {
132+ goodMoves : { moves : [ ] , probability : 0 } ,
133+ okMoves : { moves : [ ] , probability : 0 } ,
134+ blunderMoves : { moves : [ ] , probability : 0 } ,
135+ } ) ,
136+ [ ] ,
137+ )
50138
51- const mockSetHoverArrow = ( ) => {
52- // No-op for disabled analysis
53- }
139+ const emptyRecommendations = useMemo (
140+ ( ) => ( {
141+ maia : undefined ,
142+ stockfish : undefined ,
143+ } ) ,
144+ [ ] ,
145+ )
54146
55147 return (
56148 < div className = "flex h-[calc(55vh+4.5rem)] w-full flex-col gap-2" >
@@ -79,21 +171,31 @@ export const OpeningDrillAnalysis: React.FC<Props> = ({
79171 < div className = "relative" >
80172 < div className = "flex h-[calc((55vh+4.5rem)/2)]" >
81173 < Highlight
82- hover = { mockHover }
83- makeMove = { mockMakeMove }
84- currentMaiaModel = "maia_kdd_1500"
85- recommendations = { analysisController . moveRecommendations }
174+ hover = { analysisEnabled ? hover : mockHover }
175+ makeMove = { analysisEnabled ? makeMove : mockMakeMove }
176+ currentMaiaModel = { analysisController . currentMaiaModel }
177+ recommendations = {
178+ analysisEnabled
179+ ? analysisController . moveRecommendations
180+ : emptyRecommendations
181+ }
86182 moveEvaluation = {
87- analysisController . moveEvaluation || {
88- maia : undefined ,
89- stockfish : undefined ,
90- }
183+ analysisEnabled && analysisController . moveEvaluation
184+ ? analysisController . moveEvaluation
185+ : {
186+ maia : undefined ,
187+ stockfish : undefined ,
188+ }
189+ }
190+ movesByRating = {
191+ analysisEnabled ? analysisController . movesByRating : undefined
192+ }
193+ colorSanMapping = {
194+ analysisEnabled ? analysisController . colorSanMapping : { }
91195 }
92- movesByRating = { analysisController . movesByRating }
93- colorSanMapping = { analysisController . colorSanMapping }
94196 boardDescription = {
95197 analysisEnabled
96- ? 'This position offers multiple strategic options. Consider central control and piece development .'
198+ ? analysisController . boardDescription || 'Analyzing position.. .'
97199 : 'Analysis is disabled. Enable analysis to see detailed move evaluations and recommendations.'
98200 }
99201 />
@@ -118,16 +220,26 @@ export const OpeningDrillAnalysis: React.FC<Props> = ({
118220 < div className = "flex h-[calc((55vh+4.5rem)/2)] flex-row gap-2" >
119221 < div className = "flex h-full w-full flex-col" >
120222 < MoveMap
121- moveMap = { analysisController . moveMap }
122- colorSanMapping = { analysisController . colorSanMapping }
123- setHoverArrow = { mockSetHoverArrow }
223+ moveMap = { analysisEnabled ? analysisController . moveMap : undefined }
224+ colorSanMapping = {
225+ analysisEnabled ? analysisController . colorSanMapping : { }
226+ }
227+ setHoverArrow = {
228+ analysisEnabled ? setHoverArrow : mockSetHoverArrow
229+ }
124230 />
125231 </ div >
126232 < BlunderMeter
127- hover = { mockHover }
128- makeMove = { mockMakeMove }
129- data = { analysisController . blunderMeter }
130- colorSanMapping = { analysisController . colorSanMapping }
233+ hover = { analysisEnabled ? hover : mockHover }
234+ makeMove = { analysisEnabled ? makeMove : mockMakeMove }
235+ data = {
236+ analysisEnabled
237+ ? analysisController . blunderMeter
238+ : emptyBlunderMeterData
239+ }
240+ colorSanMapping = {
241+ analysisEnabled ? analysisController . colorSanMapping : { }
242+ }
131243 />
132244 </ div >
133245 { ! analysisEnabled && (
0 commit comments