@@ -9,11 +9,17 @@ import { countCachedExercises } from '@/lib/exercise-cache';
99import { validateRequestParams , getOrGenerateExercise } from './exercise-orchestrator' ;
1010import { checkRateLimit } from '@/lib/rate-limiter' ;
1111import type { ZodIssue } from 'zod' ;
12+ import { z } from 'zod' ;
1213import type { GenerateExerciseResult } from '@/lib/domain/schemas' ;
1314import { LANGUAGES } from '@/lib/domain/language' ;
1415import { getRandomTopicForLevel } from '@/lib/domain/topics' ;
1516import { getGrammarGuidance , getVocabularyGuidance } from '@/lib/domain/language-guidance' ;
1617import type { ExerciseGenerationParams } from '@/lib/domain/ai' ;
18+ import {
19+ // InitialExercisePairResultSchema, // Unused schema
20+ type InitialExercisePairResult ,
21+ GenerateExerciseResultSchema ,
22+ } from '@/lib/domain/schemas' ;
1723
1824// --- Main Action ---
1925
@@ -35,6 +41,7 @@ export const generateExerciseResponse = async (
3541 const validParams = validationResult . data ;
3642
3743 const isAllowed = checkRateLimit ( ip ) ;
44+
3845 if ( ! isAllowed ) {
3946 const cachedResult = await tryGetCachedExercise ( validParams , userId ) ;
4047 if ( cachedResult ) {
@@ -75,3 +82,84 @@ export const generateExerciseResponse = async (
7582 return createErrorResponse ( errorMessage ) ;
7683 }
7784} ;
85+
86+ // --- New Action for Initial Pair ---
87+
88+ export const generateInitialExercisePair = async (
89+ requestParams : unknown
90+ ) : Promise < InitialExercisePairResult > => {
91+ // const actionStartTime = Date.now(); // Unused var
92+ const headersList = await headers ( ) ;
93+ const ip = headersList . get ( 'x-forwarded-for' ) || 'unknown' ;
94+ const session : Session | null = await getServerSession ( authOptions ) ;
95+ const userId = getDbUserIdFromSession ( session ) ;
96+
97+ const validationResult = validateRequestParams ( requestParams ) ; // Reuse existing validation
98+ if ( ! validationResult . success ) {
99+ const errorMsg = `Invalid request parameters: ${ validationResult . error . errors
100+ . map ( ( e : ZodIssue ) => `${ e . path . join ( '.' ) } : ${ e . message } ` )
101+ . join ( ', ' ) } `;
102+ return { quizzes : [ ] , error : errorMsg } ; // Return error in the new format
103+ }
104+ const validParams = validationResult . data ;
105+
106+ // --- 2. Rate Limiting (Counts as one action call) ---
107+ const isAllowed = checkRateLimit ( ip ) ;
108+ if ( ! isAllowed ) {
109+ // Maybe try returning cached *pair*? For now, just deny.
110+ return { quizzes : [ ] , error : 'Rate limit exceeded.' } ;
111+ }
112+
113+ // --- 3. Prepare Generation Params (Same for both calls) ---
114+ const genParams : ExerciseGenerationParams = {
115+ passageLanguage : validParams . passageLanguage ,
116+ questionLanguage : validParams . questionLanguage ,
117+ level : validParams . cefrLevel ,
118+ passageLangName : LANGUAGES [ validParams . passageLanguage ] ,
119+ questionLangName : LANGUAGES [ validParams . questionLanguage ] ,
120+ topic : getRandomTopicForLevel ( validParams . cefrLevel ) ,
121+ grammarGuidance : getGrammarGuidance ( validParams . cefrLevel ) ,
122+ vocabularyGuidance : getVocabularyGuidance ( validParams . cefrLevel ) ,
123+ } ;
124+ const genParams2 = { ...genParams , topic : getRandomTopicForLevel ( validParams . cefrLevel ) } ;
125+
126+ // --- 4. Concurrent Generation Attempt ---
127+ let results : [ GenerateExerciseResult , GenerateExerciseResult ] | null = null ;
128+ let errorResult : { quizzes : [ ] ; error : string } | null = null ;
129+
130+ try {
131+ const generationPromises = [
132+ getOrGenerateExercise ( genParams , userId , 0 ) ,
133+ getOrGenerateExercise ( genParams2 , userId , 0 ) ,
134+ ] ;
135+
136+ const settledResults = await Promise . all ( generationPromises ) ;
137+
138+ if ( settledResults . every ( ( r ) => r . error === null && r . quizId !== - 1 ) ) {
139+ const validatedResults = z . array ( GenerateExerciseResultSchema ) . safeParse ( settledResults ) ;
140+ if ( validatedResults . success ) {
141+ results = validatedResults . data as [ GenerateExerciseResult , GenerateExerciseResult ] ;
142+ } else {
143+ errorResult = { quizzes : [ ] , error : 'Internal error processing generated results.' } ;
144+ }
145+ } else {
146+ const errors = settledResults . map ( ( r ) => r . error ) . filter ( ( e ) => e !== null ) ;
147+ errorResult = {
148+ quizzes : [ ] ,
149+ error : `Failed to generate exercise pair: ${ errors . join ( '; ' ) } ` ,
150+ } ;
151+ }
152+ } catch ( error ) {
153+ const message = error instanceof Error ? error . message : 'Unknown generation error' ;
154+ errorResult = { quizzes : [ ] , error : `Server error during generation: ${ message } ` } ;
155+ }
156+
157+ // --- 5. Return Result ---
158+ if ( results ) {
159+ // console.log(`[Action:InitialPair] Successfully generated pair. Total time: ${Date.now() - actionStartTime}ms`); // Remove reference to unused var
160+ return { quizzes : results , error : null } ;
161+ } else {
162+ // console.log(`[Action:InitialPair] Failed to generate pair. Total time: ${Date.now() - actionStartTime}ms`); // Remove reference to unused var
163+ return errorResult ?? { quizzes : [ ] , error : 'Unknown failure generating exercise pair.' } ;
164+ }
165+ } ;
0 commit comments