Reverted Level to only utas placement test exercises, Speaking, bug fixes, placeholder

This commit is contained in:
Carlos-Mesquita
2024-11-10 04:24:23 +00:00
parent c507eae507
commit 322d7905c3
39 changed files with 1251 additions and 279 deletions

View File

@@ -0,0 +1,125 @@
import { InteractiveSpeakingExercise, SpeakingExercise } from "@/interfaces/exam";
import { Avatar } from "../speaking";
import axios from "axios";
interface VideoResponse {
status: 'STARTED' | 'ERROR' | 'COMPLETED' | 'IN_PROGRESS';
result: string;
}
interface VideoGeneration {
index: number;
text: string;
videoId?: string;
url?: string;
}
export async function generateVideos(section: InteractiveSpeakingExercise | SpeakingExercise, focusedSection: number, selectedAvatar: Avatar | null, speakingAvatars: Avatar[]) {
const abortController = new AbortController();
let activePollingIds: string[] = [];
const avatarToUse = selectedAvatar || speakingAvatars[Math.floor(Math.random() * speakingAvatars.length)];
const pollVideoGeneration = async (videoId: string): Promise<string> => {
while (true) {
try {
const { data } = await axios.get<VideoResponse>(`api/exam/media/poll?videoId=${videoId}`, {
signal: abortController.signal
});
if (data.status === 'ERROR') {
abortController.abort();
throw new Error('Video generation failed');
}
if (data.status === 'COMPLETED') {
const videoResponse = await axios.get(data.result, {
responseType: 'blob',
signal: abortController.signal
});
const videoUrl = URL.createObjectURL(
new Blob([videoResponse.data], { type: 'video/mp4' })
);
return videoUrl;
}
await new Promise(resolve => setTimeout(resolve, 10000)); // 10 secs
} catch (error: any) {
if (error.name === 'AbortError' || axios.isCancel(error)) {
throw new Error('Operation aborted');
}
throw error;
}
}
};
const generateSingleVideo = async (text: string, index: number): Promise<VideoGeneration> => {
try {
const { data } = await axios.post<VideoResponse>('/api/exam/media/speaking',
{ text, avatar: avatarToUse.name },
{
headers: {
'Content-Type': 'application/json',
},
signal: abortController.signal
}
);
if (data.status === 'ERROR') {
abortController.abort();
throw new Error('Initial video generation failed');
}
activePollingIds.push(data.result);
const videoUrl = await pollVideoGeneration(data.result);
return { index, text, videoId: data.result, url: videoUrl };
} catch (error) {
abortController.abort();
throw error;
}
};
try {
let videosToGenerate: { text: string; index: number }[] = [];
switch (focusedSection) {
case 1: {
const interactiveSection = section as InteractiveSpeakingExercise;
videosToGenerate = interactiveSection.prompts.map((prompt, index) => ({
text: index === 0 ? prompt.text.replace("{avatar}", avatarToUse.name) : prompt.text,
index
}));
break;
}
case 2: {
const speakingSection = section as SpeakingExercise;
videosToGenerate = [{ text: `${speakingSection.text}. You have 1 minute to take notes.`, index: 0 }];
break;
}
case 3: {
const interactiveSection = section as InteractiveSpeakingExercise;
videosToGenerate = interactiveSection.prompts.map((prompt, index) => ({
text: prompt.text,
index
}));
break;
}
}
// Generate all videos concurrently
const results = await Promise.all(
videosToGenerate.map(({ text, index }) => generateSingleVideo(text, index))
);
// by order which they came in
return results.sort((a, b) => a.index - b.index);
} catch (error) {
// Clean up any ongoing requests
abortController.abort();
// Clean up any created URLs
activePollingIds.forEach(id => {
if (id) URL.revokeObjectURL(id);
});
throw error;
}
}