1212 <div class =" score-label" >{{ translate('results.outOf') }} {{ totalQuestions || '?' }}</div >
1313 </div >
1414 <div class =" processing-info" v-if =" !allProcessed" >
15- <p >{{ translate('results.processing', [formatCount(processedCount), totalQuestionCount]) }}</p >
15+ <p v-if =" !timeoutOccurred" >{{ translate('results.processing', [formatCount(processedCount), totalQuestionCount]) }}</p >
16+ <p v-else class =" timeout-warning" >{{ translate('results.timeoutWarning', [Math.floor(POLLING_TIMEOUT / 1000)]) }}</p >
1617 </div >
1718 </div >
1819
3637 <p ><strong >{{ translate('results.timeTaken') }}:</strong > {{ formatDuration(resultsData.duration_seconds) }}</p >
3738 </div >
3839
40+ <!-- Timeout Warning -->
41+ <div v-if =" timeoutOccurred" class =" timeout-warning-box" >
42+ <p >⚠️ <strong >{{ translate('results.timeoutOccurred') }}</strong > {{ translate('results.timeoutOccurredMessage') }}</p >
43+ </div >
44+
3945 <!-- AI Disclaimer -->
4046 <div class =" ai-disclaimer" >
4147 <p >⚠️ <strong >{{ translate('results.aiDisclaimer') }}</strong > {{ translate('results.aiDisclaimerText') }}</p >
@@ -114,10 +120,15 @@ export default {
114120 const processedCount = ref (0 )
115121 const isCheckingStatus = ref (false )
116122 const isPlayingAudio = ref (null ) // Track which question's audio is playing
123+ const timeoutOccurred = ref (false ) // Track if timeout has occurred
124+ const pollingStartTime = ref (null ) // Track when polling started
117125
118126 // Audio player element
119127 const audioPlayer = ref (null )
120128
129+ // Timeout configuration (60 seconds)
130+ const POLLING_TIMEOUT = 60000
131+
121132 // Start checking status when component mounts
122133 onMounted (async () => {
123134 await loadResults ()
@@ -133,16 +144,36 @@ export default {
133144 }
134145 })
135146
136- // Load results from backend (polling until all processed)
147+ // Load results from backend (polling until all processed or timeout )
137148 const loadResults = async () => {
138149 if (isCheckingStatus .value ) return
139150
151+ // Initialize polling start time if not set
152+ if (! pollingStartTime .value ) {
153+ pollingStartTime .value = Date .now ()
154+ }
155+
156+ // Check for timeout
157+ const elapsedTime = Date .now () - pollingStartTime .value
158+ if (elapsedTime > POLLING_TIMEOUT && ! timeoutOccurred .value ) {
159+ console .log (' Polling timeout reached, forcing results display' )
160+ timeoutOccurred .value = true
161+ await handleTimeout ()
162+ return
163+ }
164+
140165 try {
141166 isCheckingStatus .value = true
142167 console .log (' Loading results for session:' , props .sessionId )
143168 const response = await fetch (` /session/${ props .sessionId } /results` )
144169 console .log (' Response status:' , response .status )
145170
171+ // Check if timeout occurred while waiting for response
172+ if (timeoutOccurred .value ) {
173+ console .log (' Timeout occurred during request, stopping polling' )
174+ return
175+ }
176+
146177 if (response .ok ) {
147178 const data = await response .json ()
148179 console .log (' Results loaded:' , data)
@@ -156,25 +187,79 @@ export default {
156187 // Store total question count separately for display
157188 totalQuestionCount .value = data .total_questions
158189
159- // If not all processed, continue polling
160- if (! data .all_processed ) {
190+ // If not all processed and no timeout , continue polling
191+ if (! data .all_processed && ! timeoutOccurred . value ) {
161192 setTimeout (loadResults, 2000 )
162193 }
163194 } else {
164195 console .error (' Failed to load results:' , response .status )
165- // Continue polling on error
166- setTimeout (loadResults, 2000 )
196+ // Continue polling on error if no timeout
197+ if (! timeoutOccurred .value ) {
198+ setTimeout (loadResults, 2000 )
199+ }
167200 }
168201 } catch (error) {
169202 console .error (' Error loading results:' , error)
170- // Continue polling on error
171- setTimeout (loadResults, 2000 )
203+ // Continue polling on error if no timeout
204+ if (! timeoutOccurred .value ) {
205+ setTimeout (loadResults, 2000 )
206+ }
172207 } finally {
173208 isCheckingStatus .value = false
174209 }
175210 }
176211
177-
212+ // Handle timeout by generating failed results for unprocessed questions
213+ const handleTimeout = async () => {
214+ if (! resultsData .value ) return
215+
216+ console .log (' Handling timeout, generating failed question results' )
217+
218+ // Create a copy of the results data
219+ const modifiedResults = { ... resultsData .value }
220+
221+ // Get indices of questions that haven't been processed
222+ const processedIndices = new Set (modifiedResults .question_results .map (q => q .question_index ))
223+ const totalQuestions = modifiedResults .total_questions
224+
225+ // Generate failed results for unprocessed questions
226+ for (let i = 0 ; i < totalQuestions; i++ ) {
227+ if (! processedIndices .has (i)) {
228+ const failedResult = generateFailedQuestionResult (i)
229+ modifiedResults .question_results .push (failedResult)
230+ }
231+ }
232+
233+ // Sort questions by index
234+ modifiedResults .question_results .sort ((a , b ) => a .question_index - b .question_index )
235+
236+ // Update the results data
237+ modifiedResults .all_processed = true
238+ modifiedResults .processed_count = totalQuestions
239+ resultsData .value = modifiedResults
240+ allProcessed .value = true
241+ }
242+
243+ // Generate a failed question result
244+ const generateFailedQuestionResult = (questionIndex ) => {
245+ // Try to get question info from the original exam data
246+ // Since we don't have the full exam data, we'll create a generic failed result
247+ return {
248+ question_index: questionIndex,
249+ question_id: ` question_${ questionIndex} ` ,
250+ question_type: " unknown" ,
251+ question_text: translate (' results.questionProcessingFailed' ),
252+ score: 0 ,
253+ feedback: translate (' results.processingTimeout' ),
254+ explanation: translate (' results.timeoutExplanation' ),
255+ suggested_answer: null ,
256+ student_answer: null ,
257+ reference_answer: null ,
258+ student_audio_path: null
259+ }
260+ }
261+
262+
178263 // Start a new exam
179264 const startNewExam = () => {
180265 emit (' new-exam' )
@@ -261,6 +346,7 @@ export default {
261346 allProcessed,
262347 processedCount,
263348 isPlayingAudio,
349+ timeoutOccurred,
264350 startNewExam,
265351 goHome,
266352 formatDuration,
@@ -517,6 +603,30 @@ export default {
517603 color : #78350f ;
518604}
519605
606+ .timeout-warning {
607+ color : #dc2626 ;
608+ font-weight : 600 ;
609+ }
610+
611+ .timeout-warning-box {
612+ background : #fef2f2 ;
613+ border : 1px solid #dc2626 ;
614+ border-radius : 8px ;
615+ padding : 1rem ;
616+ margin-bottom : 2rem ;
617+ }
618+
619+ .timeout-warning-box p {
620+ margin : 0 ;
621+ color : #991b1b ;
622+ font-size : 0.9rem ;
623+ line-height : 1.5 ;
624+ }
625+
626+ .timeout-warning-box strong {
627+ color : #7f1d1d ;
628+ }
629+
520630.btn {
521631 padding : 1rem 2rem ;
522632 border : none ;
0 commit comments