Skip to content

Commit 9f4a893

Browse files
Merge pull request #104 from CSSLab/copilot/fix-103
Add backend logging for opening drill sessions
2 parents 0e28859 + 3b650f8 commit 9f4a893

6 files changed

Lines changed: 154 additions & 31 deletions

File tree

src/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ export * from './auth'
55
export * from './turing'
66
export * from './play'
77
export * from './profile'
8+
export * from './opening'

src/api/opening/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './opening'

src/api/opening/opening.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { buildUrl } from '../utils'
2+
3+
// API Types for opening drill logging
4+
export interface OpeningDrillSelection {
5+
opening_fen: string
6+
side_played: string
7+
}
8+
9+
export interface SelectOpeningDrillsRequest {
10+
openings: OpeningDrillSelection[]
11+
opponent: string
12+
num_moves: number
13+
num_drills: number
14+
}
15+
16+
export interface SelectOpeningDrillsResponse {
17+
session_id: string
18+
}
19+
20+
export interface SubmitOpeningDrillRequest {
21+
session_id: string
22+
opening_fen: string
23+
side_played: string
24+
moves_played_uci: string[]
25+
}
26+
27+
// API function to log opening drill selections and start a session
28+
export const selectOpeningDrills = async (
29+
request: SelectOpeningDrillsRequest,
30+
): Promise<SelectOpeningDrillsResponse> => {
31+
const res = await fetch(buildUrl('opening/select_opening_drills'), {
32+
method: 'POST',
33+
headers: {
34+
Accept: 'application/json',
35+
'Content-Type': 'application/json',
36+
},
37+
body: JSON.stringify(request),
38+
})
39+
40+
if (res.status === 401) {
41+
throw new Error('Unauthorized')
42+
}
43+
44+
if (!res.ok) {
45+
throw new Error(`Failed to select opening drills: ${res.statusText}`)
46+
}
47+
48+
const data = await res.json()
49+
return data as SelectOpeningDrillsResponse
50+
}
51+
52+
// API function to submit a completed opening drill
53+
export const submitOpeningDrill = async (
54+
request: SubmitOpeningDrillRequest,
55+
): Promise<void> => {
56+
const res = await fetch(buildUrl('opening/submit_opening_drill'), {
57+
method: 'POST',
58+
headers: {
59+
Accept: 'application/json',
60+
'Content-Type': 'application/json',
61+
},
62+
body: JSON.stringify(request),
63+
})
64+
65+
if (res.status === 401) {
66+
throw new Error('Unauthorized')
67+
}
68+
69+
if (!res.ok) {
70+
throw new Error(`Failed to submit opening drill: ${res.statusText}`)
71+
}
72+
}

src/components/Openings/OpeningSelectionModal.tsx

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
trackDrillConfigurationCompleted,
2424
} from 'src/lib/analytics'
2525
import { MAIA_MODELS_WITH_NAMES } from 'src/constants/common'
26+
import { selectOpeningDrills } from 'src/api/opening'
2627

2728
type MobileTab = 'browse' | 'selected'
2829

@@ -1029,41 +1030,70 @@ export const OpeningSelectionModal: React.FC<Props> = ({
10291030
return sequence.sort(() => Math.random() - 0.5)
10301031
}
10311032

1032-
const handleStartDrilling = () => {
1033+
const handleStartDrilling = async () => {
10331034
if (selections.length > 0) {
10341035
const drillSequence = generateDrillSequence(selections, drillCount)
1035-
const configuration: DrillConfiguration = {
1036-
selections,
1037-
drillCount,
1038-
drillSequence,
1039-
}
10401036

1041-
// Track drill configuration completion
1042-
const uniqueOpenings = new Set(selections.map((s) => s.opening.id)).size
1043-
const averageTargetMoves =
1044-
selections.reduce((sum, s) => sum + s.targetMoveNumber, 0) /
1045-
selections.length
1046-
const maiaVersionsUsed = [
1047-
...new Set(selections.map((s) => s.maiaVersion)),
1048-
]
1049-
const colorDistribution = selections.reduce(
1050-
(acc, s) => {
1051-
acc[s.playerColor]++
1052-
return acc
1053-
},
1054-
{ white: 0, black: 0 },
1055-
)
1037+
try {
1038+
// Prepare API request data
1039+
const openings = selections.map((selection) => ({
1040+
opening_fen: selection.variation
1041+
? selection.variation.fen
1042+
: selection.opening.fen,
1043+
side_played: selection.playerColor,
1044+
}))
1045+
1046+
// Call the backend API to log opening selections and get session ID
1047+
const response = await selectOpeningDrills({
1048+
openings,
1049+
opponent: selectedMaiaVersion.id,
1050+
num_moves: targetMoveNumber,
1051+
num_drills: drillCount,
1052+
})
1053+
1054+
const configuration: DrillConfiguration = {
1055+
selections,
1056+
drillCount,
1057+
drillSequence,
1058+
sessionId: response.session_id,
1059+
}
10561060

1057-
trackDrillConfigurationCompleted(
1058-
selections.length,
1059-
drillCount,
1060-
uniqueOpenings,
1061-
averageTargetMoves,
1062-
maiaVersionsUsed,
1063-
colorDistribution,
1064-
)
1061+
// Track drill configuration completion
1062+
const uniqueOpenings = new Set(selections.map((s) => s.opening.id)).size
1063+
const averageTargetMoves =
1064+
selections.reduce((sum, s) => sum + s.targetMoveNumber, 0) /
1065+
selections.length
1066+
const maiaVersionsUsed = [
1067+
...new Set(selections.map((s) => s.maiaVersion)),
1068+
]
1069+
const colorDistribution = selections.reduce(
1070+
(acc, s) => {
1071+
acc[s.playerColor]++
1072+
return acc
1073+
},
1074+
{ white: 0, black: 0 },
1075+
)
10651076

1066-
onComplete(configuration)
1077+
trackDrillConfigurationCompleted(
1078+
selections.length,
1079+
drillCount,
1080+
uniqueOpenings,
1081+
averageTargetMoves,
1082+
maiaVersionsUsed,
1083+
colorDistribution,
1084+
)
1085+
1086+
onComplete(configuration)
1087+
} catch (error) {
1088+
console.error('Failed to start drilling session:', error)
1089+
// Still allow the drill to start even if API call fails
1090+
const configuration: DrillConfiguration = {
1091+
selections,
1092+
drillCount,
1093+
drillSequence,
1094+
}
1095+
onComplete(configuration)
1096+
}
10671097
}
10681098
}
10691099

src/hooks/useOpeningDrillController/useOpeningDrillController.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
22
import { Chess } from 'chess.ts'
33
import { getGameMove } from 'src/api/play/play'
4+
import { submitOpeningDrill } from 'src/api/opening'
45
import { useTreeController } from '../useTreeController'
56
import { useLocalStorage } from '../useLocalStorage'
67
import {
@@ -564,6 +565,23 @@ export const useOpeningDrillController = (
564565
}
565566
})
566567

568+
// Submit drill data to backend if session ID is available
569+
if (configuration.sessionId) {
570+
try {
571+
await submitOpeningDrill({
572+
session_id: configuration.sessionId,
573+
opening_fen: drillGame.selection.variation
574+
? drillGame.selection.variation.fen
575+
: drillGame.selection.opening.fen,
576+
side_played: drillGame.selection.playerColor,
577+
moves_played_uci: drillGame.moves,
578+
})
579+
} catch (error) {
580+
console.error('Failed to submit drill to backend:', error)
581+
// Continue even if backend submission fails
582+
}
583+
}
584+
567585
setShowPerformanceModal(true)
568586
} catch (error) {
569587
console.error('Error completing drill analysis:', error)
@@ -572,7 +590,7 @@ export const useOpeningDrillController = (
572590
setIsAnalyzingDrill(false)
573591
}
574592
},
575-
[currentDrillGame, evaluateDrillPerformance],
593+
[currentDrillGame, evaluateDrillPerformance, configuration.sessionId],
576594
)
577595

578596
const moveToNextDrill = useCallback(() => {

src/types/openings/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface DrillConfiguration {
2929
selections: OpeningSelection[]
3030
drillCount: number
3131
drillSequence: OpeningSelection[]
32+
sessionId?: string
3233
}
3334

3435
export interface OpeningDrillState {

0 commit comments

Comments
 (0)