Skip to content

Commit fee8f09

Browse files
feat: add initial iteration of "Learn from Mistakes" feature
1 parent 57d3690 commit fee8f09

File tree

10 files changed

+676
-30
lines changed

10 files changed

+676
-30
lines changed

src/components/Analysis/ConfigurableScreens.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ interface Props {
1313
currentNode: GameNode
1414
onDeleteCustomGame?: () => void
1515
onAnalyzeEntireGame?: () => void
16+
onLearnFromMistakes?: () => void
1617
isAnalysisInProgress?: boolean
18+
isLearnFromMistakesActive?: boolean
1719
autoSave?: {
1820
hasUnsavedChanges: boolean
1921
isSaving: boolean
@@ -30,7 +32,9 @@ export const ConfigurableScreens: React.FC<Props> = ({
3032
currentNode,
3133
onDeleteCustomGame,
3234
onAnalyzeEntireGame,
35+
onLearnFromMistakes,
3336
isAnalysisInProgress,
37+
isLearnFromMistakesActive,
3438
autoSave,
3539
}) => {
3640
const screens = [
@@ -87,7 +91,9 @@ export const ConfigurableScreens: React.FC<Props> = ({
8791
game={game}
8892
onDeleteCustomGame={onDeleteCustomGame}
8993
onAnalyzeEntireGame={onAnalyzeEntireGame}
94+
onLearnFromMistakes={onLearnFromMistakes}
9095
isAnalysisInProgress={isAnalysisInProgress}
96+
isLearnFromMistakesActive={isLearnFromMistakesActive}
9197
autoSave={autoSave}
9298
/>
9399
) : screen.id === 'export' ? (

src/components/Analysis/ConfigureAnalysis.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ interface Props {
1111
game: AnalyzedGame
1212
onDeleteCustomGame?: () => void
1313
onAnalyzeEntireGame?: () => void
14+
onLearnFromMistakes?: () => void
1415
isAnalysisInProgress?: boolean
16+
isLearnFromMistakesActive?: boolean
1517
autoSave?: {
1618
hasUnsavedChanges: boolean
1719
isSaving: boolean
@@ -27,7 +29,9 @@ export const ConfigureAnalysis: React.FC<Props> = ({
2729
game,
2830
onDeleteCustomGame,
2931
onAnalyzeEntireGame,
32+
onLearnFromMistakes,
3033
isAnalysisInProgress = false,
34+
isLearnFromMistakesActive = false,
3135
autoSave,
3236
}: Props) => {
3337
const isCustomGame = game.type === 'custom-pgn' || game.type === 'custom-fen'
@@ -55,8 +59,8 @@ export const ConfigureAnalysis: React.FC<Props> = ({
5559
{onAnalyzeEntireGame && (
5660
<button
5761
onClick={onAnalyzeEntireGame}
58-
disabled={isAnalysisInProgress}
59-
className="flex w-full items-center gap-1.5 rounded-sm bg-human-4/60 !px-2 !py-1 !text-sm text-primary/70 transition duration-200 hover:bg-human-4/80 hover:text-primary"
62+
disabled={isAnalysisInProgress || isLearnFromMistakesActive}
63+
className="flex w-full items-center gap-1.5 rounded-sm bg-human-4/60 !px-2 !py-1 !text-sm text-primary/70 transition duration-200 hover:bg-human-4/80 hover:text-primary disabled:cursor-not-allowed disabled:opacity-50"
6064
>
6165
<div className="flex items-center justify-center gap-1.5">
6266
<span className="material-symbols-outlined !text-sm">
@@ -70,6 +74,22 @@ export const ConfigureAnalysis: React.FC<Props> = ({
7074
</div>
7175
</button>
7276
)}
77+
{onLearnFromMistakes && (
78+
<button
79+
onClick={onLearnFromMistakes}
80+
disabled={isAnalysisInProgress || isLearnFromMistakesActive}
81+
className="flex w-full items-center gap-1.5 rounded-sm bg-human-4/60 !px-2 !py-1 !text-sm text-primary/70 transition duration-200 hover:bg-human-4/80 hover:text-primary disabled:cursor-not-allowed disabled:opacity-50"
82+
>
83+
<div className="flex items-center justify-center gap-1.5">
84+
<span className="material-symbols-outlined !text-sm">school</span>
85+
<span className="text-xs">
86+
{isLearnFromMistakesActive
87+
? 'Learning in progress...'
88+
: 'Learn from mistakes'}
89+
</span>
90+
</div>
91+
</button>
92+
)}
7393
{autoSave &&
7494
game.type !== 'custom-pgn' &&
7595
game.type !== 'custom-fen' &&
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import React from 'react'
2+
import { LearnFromMistakesState, MistakePosition } from 'src/types/analysis'
3+
4+
interface Props {
5+
state: LearnFromMistakesState
6+
currentInfo: {
7+
mistake: MistakePosition
8+
progress: string
9+
isLastMistake: boolean
10+
} | null
11+
onShowSolution: () => void
12+
onNext: () => void
13+
onStop: () => void
14+
lastMoveResult?: 'correct' | 'incorrect' | 'not-learning'
15+
}
16+
17+
export const LearnFromMistakes: React.FC<Props> = ({
18+
state,
19+
currentInfo,
20+
onShowSolution,
21+
onNext,
22+
onStop,
23+
lastMoveResult,
24+
}) => {
25+
if (!state.isActive || !currentInfo) {
26+
return null
27+
}
28+
29+
const { mistake, progress, isLastMistake } = currentInfo
30+
31+
const getMoveDisplay = () => {
32+
const moveNumber = Math.ceil((mistake.moveIndex + 1) / 2)
33+
const isWhiteMove = mistake.moveIndex % 2 === 0
34+
35+
if (isWhiteMove) {
36+
return `${moveNumber}. ${mistake.san}`
37+
} else {
38+
return `${moveNumber}... ${mistake.san}`
39+
}
40+
}
41+
42+
const getPromptText = () => {
43+
const mistakeType = mistake.type === 'blunder' ? '??' : '?!'
44+
const moveDisplay = getMoveDisplay()
45+
const playerColorName = mistake.playerColor === 'white' ? 'White' : 'Black'
46+
47+
return `${moveDisplay}${mistakeType} was played. Find a better move for ${playerColorName}.`
48+
}
49+
50+
const getFeedbackText = () => {
51+
if (state.showSolution) {
52+
return `Correct! ${mistake.bestMoveSan} was the best move.`
53+
}
54+
55+
if (lastMoveResult === 'incorrect') {
56+
const playerColorName =
57+
mistake.playerColor === 'white' ? 'White' : 'Black'
58+
return `You can do better. Try another move for ${playerColorName}.`
59+
}
60+
61+
return null
62+
}
63+
64+
return (
65+
<div className="fixed bottom-4 left-1/2 z-50 w-full max-w-2xl -translate-x-1/2 transform rounded-lg border border-white/10 bg-background-1 p-4 shadow-lg">
66+
<div className="flex flex-col gap-3">
67+
{/* Header */}
68+
<div className="flex items-center justify-between">
69+
<div className="flex items-center gap-2">
70+
<span className="material-symbols-outlined text-xl text-human-4">
71+
school
72+
</span>
73+
<h3 className="font-semibold text-primary">Learn from Mistakes</h3>
74+
<span className="text-xs text-secondary">({progress})</span>
75+
</div>
76+
<button
77+
onClick={onStop}
78+
className="flex items-center gap-1 rounded bg-background-2 px-2 py-1 text-xs text-secondary transition duration-200 hover:bg-background-3 hover:text-primary"
79+
>
80+
<span className="material-symbols-outlined !text-sm">close</span>
81+
Stop
82+
</button>
83+
</div>
84+
85+
{/* Main prompt */}
86+
<div className="rounded bg-background-2/50 p-3">
87+
<p className="text-sm text-primary">{getPromptText()}</p>
88+
{getFeedbackText() && (
89+
<p
90+
className={`mt-2 text-sm ${
91+
state.showSolution
92+
? 'text-green-400'
93+
: lastMoveResult === 'incorrect'
94+
? 'text-orange-400'
95+
: 'text-primary'
96+
}`}
97+
>
98+
{getFeedbackText()}
99+
</p>
100+
)}
101+
</div>
102+
103+
{/* Action buttons */}
104+
<div className="flex gap-2">
105+
{!state.showSolution ? (
106+
<button
107+
onClick={onShowSolution}
108+
className="flex items-center gap-1.5 rounded bg-human-4/60 px-3 py-1.5 text-sm text-primary/70 transition duration-200 hover:bg-human-4/80 hover:text-primary"
109+
>
110+
<span className="material-symbols-outlined !text-sm">
111+
lightbulb
112+
</span>
113+
Show me the solution
114+
</button>
115+
) : (
116+
<button
117+
onClick={onNext}
118+
className="flex items-center gap-1.5 rounded bg-human-4/60 px-3 py-1.5 text-sm text-primary/70 transition duration-200 hover:bg-human-4/80 hover:text-primary"
119+
>
120+
<span className="material-symbols-outlined !text-sm">
121+
{isLastMistake ? 'check' : 'arrow_forward'}
122+
</span>
123+
{isLastMistake ? 'Finish' : 'Next mistake'}
124+
</button>
125+
)}
126+
127+
{state.showSolution && !isLastMistake && (
128+
<button
129+
onClick={onNext}
130+
className="flex items-center gap-1.5 rounded bg-background-2 px-3 py-1.5 text-sm text-secondary transition duration-200 hover:bg-background-3 hover:text-primary"
131+
>
132+
<span className="material-symbols-outlined !text-sm">
133+
skip_next
134+
</span>
135+
Skip to next
136+
</button>
137+
)}
138+
</div>
139+
</div>
140+
</div>
141+
)
142+
}

src/components/Analysis/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from './AnalysisNotification'
1212
export * from './AnalysisOverlay'
1313
export * from './InteractiveDescription'
1414
export * from './AnalysisSidebar'
15+
export * from './LearnFromMistakes'

src/constants/analysis.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const MOVE_CLASSIFICATION_THRESHOLDS = {
1919

2020
export const DEFAULT_MAIA_MODEL = 'maia_kdd_1500' as const
2121
export const MIN_STOCKFISH_DEPTH = 12 as const
22+
export const LEARN_FROM_MISTAKES_DEPTH = 15 as const
2223

2324
export const COLORS = {
2425
good: ['#238b45', '#41ab5d', '#74c476', '#90D289', '#AEDFA4'],

0 commit comments

Comments
 (0)