Added Speaking to level, fixed a bug where it was causing level to crash if the listening was already created and the section was switched, added true false exercises to listening
This commit is contained in:
@@ -8,7 +8,7 @@ import GenLoader from "../Shared/GenLoader";
|
||||
import { useEffect, useState } from "react";
|
||||
import useSectionEdit from "../../Hooks/useSectionEdit";
|
||||
import useExamEditorStore from "@/stores/examEditor";
|
||||
import { InteractiveSpeakingExercise } from "@/interfaces/exam";
|
||||
import { InteractiveSpeakingExercise, LevelPart } from "@/interfaces/exam";
|
||||
import { BsFileText } from "react-icons/bs";
|
||||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa6";
|
||||
import { RiVideoLine } from "react-icons/ri";
|
||||
@@ -23,10 +23,9 @@ interface Props {
|
||||
const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }) => {
|
||||
const { dispatch } = useExamEditorStore();
|
||||
const [local, setLocal] = useState(exercise);
|
||||
|
||||
const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
|
||||
|
||||
const { generating, genResult, state } = useExamEditorStore(
|
||||
const { generating, genResult, state, levelGenResults, levelGenerating } = useExamEditorStore(
|
||||
(state) => state.modules[module].sections.find((section) => section.sectionId === sectionId)!
|
||||
);
|
||||
|
||||
@@ -34,48 +33,94 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "s
|
||||
sectionId,
|
||||
onSave: () => {
|
||||
setEditing(false);
|
||||
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId: sectionId, update: local, module: module } });
|
||||
|
||||
if (module === "level") {
|
||||
const updatedState = {
|
||||
...state,
|
||||
exercises: (state as LevelPart).exercises.map((ex) =>
|
||||
ex.id === local.id ? local : ex
|
||||
)
|
||||
};
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedState, module }
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: local, module }
|
||||
});
|
||||
}
|
||||
|
||||
if (genResult) {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId,
|
||||
module: module,
|
||||
module,
|
||||
field: "genResult",
|
||||
value: undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const speakingScript = levelGenResults?.find((res) => res.generating === `${local.id}-speakingScript`);
|
||||
if (module === "level" && speakingScript) {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId,
|
||||
field: "levelGenResults",
|
||||
value: levelGenResults.filter((res) => res.generating !== `${local.id}-speakingScript`),
|
||||
module
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onDiscard: () => {
|
||||
setLocal(exercise);
|
||||
},
|
||||
onPractice: () => {
|
||||
const updatedExercise = {
|
||||
...state,
|
||||
isPractice: !local.isPractice
|
||||
};
|
||||
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
|
||||
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: module } });
|
||||
const updatedLocal = { ...local, isPractice: !local.isPractice };
|
||||
setLocal(updatedLocal);
|
||||
|
||||
if (module === "level") {
|
||||
const updatedState = {
|
||||
...state,
|
||||
exercises: (state as LevelPart).exercises.map((ex) =>
|
||||
ex.id === local.id ? updatedLocal : ex
|
||||
)
|
||||
};
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedState, module }
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedLocal, module }
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (genResult && generating === "speakingScript") {
|
||||
setEditing(true);
|
||||
setLocal({
|
||||
const updatedLocal = {
|
||||
...local,
|
||||
title: genResult.result[0].title,
|
||||
prompts: genResult.result[0].prompts.map((item: any) => ({
|
||||
text: item || "",
|
||||
video_url: ""
|
||||
}))
|
||||
});
|
||||
};
|
||||
setEditing(true);
|
||||
setLocal(updatedLocal);
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId,
|
||||
module: module,
|
||||
module,
|
||||
field: "generating",
|
||||
value: undefined
|
||||
}
|
||||
@@ -84,6 +129,90 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "s
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [genResult, generating]);
|
||||
|
||||
useEffect(() => {
|
||||
if (genResult && generating === "video") {
|
||||
const updatedLocal = { ...local, prompts: genResult.result[0].prompts };
|
||||
setLocal(updatedLocal);
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedLocal, module }
|
||||
});
|
||||
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId,
|
||||
module,
|
||||
field: "generating",
|
||||
value: undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [genResult, generating]);
|
||||
|
||||
useEffect(() => {
|
||||
const speakingScript = levelGenResults?.find((res) => res.generating === `${local.id}-speakingScript`);
|
||||
const isGenerating = levelGenerating?.includes(`${local.id}-speakingScript`);
|
||||
|
||||
if (speakingScript && isGenerating) {
|
||||
const updatedLocal = {
|
||||
...local,
|
||||
title: speakingScript.result[0].title,
|
||||
prompts: speakingScript.result[0].prompts.map((item: any) => ({
|
||||
text: item || "",
|
||||
video_url: ""
|
||||
}))
|
||||
};
|
||||
setEditing(true);
|
||||
setLocal(updatedLocal);
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId,
|
||||
field: "levelGenerating",
|
||||
value: levelGenerating.filter((g) => g !== `${local.id}-speakingScript`),
|
||||
module
|
||||
}
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [levelGenResults, levelGenerating]);
|
||||
|
||||
useEffect(() => {
|
||||
const speakingVideo = levelGenResults?.find((res) => res.generating === `${local.id}-video`);
|
||||
const isGenerating = levelGenerating?.includes(`${local.id}-video`);
|
||||
|
||||
if (speakingVideo && isGenerating) {
|
||||
const updatedLocal = { ...local, prompts: speakingVideo.result[0].prompts };
|
||||
setLocal(updatedLocal);
|
||||
|
||||
const updatedState = {
|
||||
...state,
|
||||
exercises: (state as LevelPart).exercises.map((ex) =>
|
||||
ex.id === local.id ? updatedLocal : ex
|
||||
)
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedState, module }
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId,
|
||||
field: "levelGenerating",
|
||||
value: levelGenerating.filter((g) => g !== `${local.id}-video`),
|
||||
module
|
||||
}
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [levelGenResults, levelGenerating]);
|
||||
|
||||
const addPrompt = () => {
|
||||
setLocal(prev => ({
|
||||
...prev,
|
||||
@@ -150,7 +279,7 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "s
|
||||
module="speaking"
|
||||
/>
|
||||
</div>
|
||||
{generating && generating === "speakingScript" ? (
|
||||
{(generating && generating === "speakingScript") || (levelGenerating.find((g) => g === `${local.id}-speakingScript`)) ? (
|
||||
<GenLoader module={module} />
|
||||
) : (
|
||||
<>
|
||||
@@ -206,7 +335,7 @@ const InteractiveSpeaking: React.FC<Props> = ({ sectionId, exercise, module = "s
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
{generating && generating === "video" &&
|
||||
{(generating && generating === "video") || levelGenerating.find((g) => g === `${local.id}-video`) &&
|
||||
<GenLoader module={module} custom="Generating the videos ... This may take a while ..." />
|
||||
}
|
||||
<Card>
|
||||
|
||||
@@ -6,7 +6,7 @@ import Header from "../../Shared/Header";
|
||||
import GenLoader from "../Shared/GenLoader";
|
||||
import useSectionEdit from "../../Hooks/useSectionEdit";
|
||||
import useExamEditorStore from "@/stores/examEditor";
|
||||
import { InteractiveSpeakingExercise } from "@/interfaces/exam";
|
||||
import { InteractiveSpeakingExercise, LevelPart } from "@/interfaces/exam";
|
||||
import { BsFileText } from "react-icons/bs";
|
||||
import { RiVideoLine } from 'react-icons/ri';
|
||||
import { FaChevronLeft, FaChevronRight } from 'react-icons/fa6';
|
||||
@@ -19,7 +19,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }) => {
|
||||
const {dispatch} = useExamEditorStore();
|
||||
const { dispatch } = useExamEditorStore();
|
||||
const [local, setLocal] = useState(() => {
|
||||
const defaultPrompts = [
|
||||
{ text: "Hello my name is {avatar}, what is yours?", video_url: "" },
|
||||
@@ -31,7 +31,7 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
|
||||
const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
|
||||
|
||||
const { generating, genResult , state} = useExamEditorStore(
|
||||
const { generating, genResult, state, levelGenResults, levelGenerating } = useExamEditorStore(
|
||||
(state) => state.modules[module].sections.find((section) => section.sectionId === sectionId)!
|
||||
);
|
||||
|
||||
@@ -39,18 +39,48 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
sectionId,
|
||||
onSave: () => {
|
||||
setEditing(false);
|
||||
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId: sectionId, update: local, module } });
|
||||
|
||||
if (module === "level") {
|
||||
const updatedState = {
|
||||
...state,
|
||||
exercises: (state as LevelPart).exercises.map((ex) =>
|
||||
ex.id === local.id ? local : ex
|
||||
)
|
||||
};
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedState, module }
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: local, module }
|
||||
});
|
||||
}
|
||||
|
||||
if (genResult) {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId,
|
||||
module: module,
|
||||
module,
|
||||
field: "genResult",
|
||||
value: undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const speakingScript = levelGenResults?.find((res) => res.generating === `${local.id}-speakingScript`);
|
||||
if (module === "level" && speakingScript) {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId,
|
||||
field: "levelGenResults",
|
||||
value: levelGenResults.filter((res) => res.generating !== `${local.id}-speakingScript`),
|
||||
module
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onDiscard: () => {
|
||||
setLocal({
|
||||
@@ -63,36 +93,52 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
});
|
||||
},
|
||||
onPractice: () => {
|
||||
const updatedExercise = {
|
||||
...state,
|
||||
isPractice: !local.isPractice
|
||||
};
|
||||
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
|
||||
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: module } });
|
||||
const updatedLocal = { ...local, isPractice: !local.isPractice };
|
||||
setLocal(updatedLocal);
|
||||
|
||||
if (module === "level") {
|
||||
const updatedState = {
|
||||
...state,
|
||||
exercises: (state as LevelPart).exercises.map((ex) =>
|
||||
ex.id === local.id ? updatedLocal : ex
|
||||
)
|
||||
};
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedState, module }
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedLocal, module }
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (genResult && generating === "speakingScript") {
|
||||
setEditing(true);
|
||||
setLocal(prev => ({
|
||||
...prev,
|
||||
const updatedLocal = {
|
||||
...local,
|
||||
first_title: genResult.result[0].first_topic,
|
||||
second_title: genResult.result[0].second_topic,
|
||||
prompts: [
|
||||
prev.prompts[0],
|
||||
prev.prompts[1],
|
||||
local.prompts[0],
|
||||
local.prompts[1],
|
||||
...genResult.result[0].prompts.map((item: any) => ({
|
||||
text: item,
|
||||
video_url: ""
|
||||
}))
|
||||
]
|
||||
}));
|
||||
};
|
||||
setEditing(true);
|
||||
setLocal(updatedLocal);
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId,
|
||||
module: module,
|
||||
module,
|
||||
field: "generating",
|
||||
value: undefined
|
||||
}
|
||||
@@ -101,6 +147,96 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [genResult, generating]);
|
||||
|
||||
useEffect(() => {
|
||||
if (genResult && generating === "video") {
|
||||
const updatedLocal = { ...local, prompts: genResult.result[0].prompts };
|
||||
setLocal(updatedLocal);
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedLocal, module }
|
||||
});
|
||||
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId,
|
||||
module,
|
||||
field: "generating",
|
||||
value: undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [genResult, generating]);
|
||||
|
||||
useEffect(() => {
|
||||
const speakingScript = levelGenResults?.find((res) => res.generating === `${local.id}-speakingScript`);
|
||||
const isGenerating = levelGenerating?.includes(`${local.id}-speakingScript`);
|
||||
|
||||
if (speakingScript && isGenerating) {
|
||||
const updatedLocal = {
|
||||
...local,
|
||||
first_title: speakingScript.result[0].first_topic,
|
||||
second_title: speakingScript.result[0].second_topic,
|
||||
prompts: [
|
||||
local.prompts[0],
|
||||
local.prompts[1],
|
||||
...speakingScript.result[0].prompts.map((item: any) => ({
|
||||
text: item,
|
||||
video_url: ""
|
||||
}))
|
||||
]
|
||||
};
|
||||
setEditing(true);
|
||||
setLocal(updatedLocal);
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId,
|
||||
field: "levelGenerating",
|
||||
value: levelGenerating.filter((g) => g !== `${local.id}-speakingScript`),
|
||||
module
|
||||
}
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [levelGenResults, levelGenerating]);
|
||||
|
||||
useEffect(() => {
|
||||
const speakingVideo = levelGenResults?.find((res) => res.generating === `${local.id}-video`);
|
||||
const isGenerating = levelGenerating?.includes(`${local.id}-video`);
|
||||
|
||||
if (speakingVideo && isGenerating) {
|
||||
const updatedLocal = { ...local, video_url: speakingVideo.result[0].video_url };
|
||||
setLocal(updatedLocal);
|
||||
|
||||
const updatedState = {
|
||||
...state,
|
||||
exercises: (state as LevelPart).exercises.map((ex) =>
|
||||
ex.id === local.id ? updatedLocal : ex
|
||||
)
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedState, module }
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId,
|
||||
field: "levelGenerating",
|
||||
value: levelGenerating.filter((g) => g !== `${local.id}-video`),
|
||||
module
|
||||
}
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [levelGenResults, levelGenerating]);
|
||||
|
||||
|
||||
const addPrompt = () => {
|
||||
setLocal(prev => ({
|
||||
...prev,
|
||||
@@ -159,7 +295,7 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
};
|
||||
|
||||
const handleNextVideo = () => {
|
||||
setCurrentVideoIndex((prev) =>
|
||||
setCurrentVideoIndex((prev) =>
|
||||
(prev < local.prompts.length - 1 ? prev + 1 : prev)
|
||||
);
|
||||
};
|
||||
@@ -179,7 +315,7 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
module="speaking"
|
||||
/>
|
||||
</div>
|
||||
{generating && generating === "speakingScript" ? (
|
||||
{(generating && generating === "speakingScript") || (levelGenerating.find((g) => g === `${local.id}-speakingScript`)) ? (
|
||||
<GenLoader module={module} />
|
||||
) : (
|
||||
<>
|
||||
@@ -287,8 +423,8 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
onClick={handlePrevVideo}
|
||||
disabled={currentVideoIndex === 0}
|
||||
className={`p-2 rounded-full ${currentVideoIndex === 0
|
||||
? 'text-gray-400 cursor-not-allowed'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
? 'text-gray-400 cursor-not-allowed'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
<FaChevronLeft className="w-4 h-4" />
|
||||
@@ -300,8 +436,8 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
onClick={handleNextVideo}
|
||||
disabled={currentVideoIndex === local.prompts.length - 1}
|
||||
className={`p-2 rounded-full ${currentVideoIndex === local.prompts.length - 1
|
||||
? 'text-gray-400 cursor-not-allowed'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
? 'text-gray-400 cursor-not-allowed'
|
||||
: 'text-gray-600 hover:bg-gray-100'
|
||||
}`}
|
||||
>
|
||||
<FaChevronRight className="w-4 h-4" />
|
||||
@@ -323,7 +459,7 @@ const Speaking1: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
{generating && generating === "video" &&
|
||||
{(generating && generating === "video") || levelGenerating.find((g) => g === `${local.id}-video`) &&
|
||||
<GenLoader module={module} custom="Generating the videos ... This may take a while ..." />
|
||||
}
|
||||
<Card>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { AiOutlinePlus, AiOutlineDelete } from 'react-icons/ai';
|
||||
import { SpeakingExercise } from "@/interfaces/exam";
|
||||
import { LevelPart, SpeakingExercise } from "@/interfaces/exam";
|
||||
import useExamEditorStore from "@/stores/examEditor";
|
||||
import { useEffect, useState } from "react";
|
||||
import useSectionEdit from "../../Hooks/useSectionEdit";
|
||||
@@ -20,61 +20,104 @@ interface Props {
|
||||
module?: Module;
|
||||
}
|
||||
|
||||
|
||||
const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }) => {
|
||||
const { dispatch } = useExamEditorStore();
|
||||
const [local, setLocal] = useState(exercise);
|
||||
|
||||
const { generating, genResult, state } = useExamEditorStore(
|
||||
(state) => state.modules[module].sections.find((section) => section.sectionId === sectionId)!
|
||||
);
|
||||
const { sections } = useExamEditorStore((store) => store.modules[module]);
|
||||
const section = sections.find((section) => section.sectionId === sectionId)!;
|
||||
const { generating, genResult, state, levelGenResults, levelGenerating } = section;
|
||||
|
||||
const { editing, setEditing, handleSave, handleDiscard, handleEdit, handlePractice } = useSectionEdit({
|
||||
sectionId,
|
||||
onSave: () => {
|
||||
setEditing(false);
|
||||
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId: sectionId, update: local , module} });
|
||||
|
||||
if (module === "level") {
|
||||
const updatedState = {
|
||||
...state,
|
||||
exercises: (state as LevelPart).exercises.map((ex) =>
|
||||
ex.id === local.id ? local : ex
|
||||
)
|
||||
};
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedState, module }
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: local, module }
|
||||
});
|
||||
}
|
||||
|
||||
if (genResult) {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId,
|
||||
module: module,
|
||||
module,
|
||||
field: "genResult",
|
||||
value: undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const speakingScript = levelGenResults.find((res) => res.generating === `${local.id}-speakingScript`)
|
||||
if (module === "level" && speakingScript) {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId,
|
||||
field: "levelGenResults",
|
||||
value: section!.levelGenResults.filter((res) => res.generating !== `${local.id ? `${local.id}-` : ''}speakingScript`),
|
||||
module
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
onDiscard: () => {
|
||||
setLocal(exercise);
|
||||
},
|
||||
onPractice: () => {
|
||||
const updatedExercise = {
|
||||
...state,
|
||||
isPractice: !local.isPractice
|
||||
};
|
||||
setLocal((prev) => ({...prev, isPractice: !local.isPractice}))
|
||||
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: updatedExercise, module: module } });
|
||||
const updatedLocal = { ...local, isPractice: !local.isPractice };
|
||||
setLocal(updatedLocal);
|
||||
|
||||
if (module === "level") {
|
||||
const updatedState = {
|
||||
...state,
|
||||
exercises: (state as LevelPart).exercises.map((ex) =>
|
||||
ex.id === local.id ? updatedLocal : ex
|
||||
)
|
||||
};
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedState, module }
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedLocal, module }
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (genResult && generating === "speakingScript") {
|
||||
setEditing(true);
|
||||
setLocal({
|
||||
const updatedLocal = {
|
||||
...local,
|
||||
title: genResult.result[0].topic,
|
||||
text: genResult.result[0].question,
|
||||
prompts: genResult.result[0].prompts
|
||||
});
|
||||
};
|
||||
setEditing(true);
|
||||
setLocal(updatedLocal);
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId,
|
||||
module: module,
|
||||
module,
|
||||
field: "generating",
|
||||
value: undefined
|
||||
}
|
||||
@@ -85,13 +128,19 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
|
||||
useEffect(() => {
|
||||
if (genResult && generating === "video") {
|
||||
setLocal({...local, video_url: genResult.result[0].video_url});
|
||||
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: {...local, video_url: genResult.result[0].video_url} , module} });
|
||||
const updatedLocal = { ...local, video_url: genResult.result[0].video_url };
|
||||
setLocal(updatedLocal);
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedLocal, module }
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD",
|
||||
payload: {
|
||||
sectionId,
|
||||
module: module,
|
||||
module,
|
||||
field: "generating",
|
||||
value: undefined
|
||||
}
|
||||
@@ -100,6 +149,62 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [genResult, generating]);
|
||||
|
||||
useEffect(() => {
|
||||
const speakingScript = levelGenResults.find((res) => res.generating === `${local.id}-speakingScript`);
|
||||
const generating = levelGenerating.find((res) => res === `${local.id}-speakingScript`);
|
||||
if (speakingScript && generating) {
|
||||
const updatedLocal = {
|
||||
...local,
|
||||
title: speakingScript.result[0].topic,
|
||||
text: speakingScript.result[0].question,
|
||||
prompts: speakingScript.result[0].prompts
|
||||
};
|
||||
setEditing(true);
|
||||
setLocal(updatedLocal);
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId,
|
||||
field: "levelGenerating",
|
||||
value: section!.levelGenerating.filter((g) => g !== `${local.id ? `${local.id}-` : ''}speakingScript`),
|
||||
module
|
||||
}
|
||||
})
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [levelGenResults, levelGenerating]);
|
||||
|
||||
useEffect(() => {
|
||||
const speakingVideo = levelGenResults.find((res) => res.generating === `${local.id}-video`);
|
||||
const generating = levelGenerating.find((res) => res === `${local.id}-video`);
|
||||
|
||||
if (speakingVideo && generating) {
|
||||
const updatedLocal = { ...local, video_url: speakingVideo.result[0].video_url };
|
||||
setLocal(updatedLocal);
|
||||
|
||||
const updatedState = {
|
||||
...state,
|
||||
exercises: (state as LevelPart).exercises.map((ex) =>
|
||||
ex.id === local.id ? updatedLocal : ex
|
||||
)
|
||||
};
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_STATE",
|
||||
payload: { sectionId, update: updatedState, module }
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: "UPDATE_SECTION_SINGLE_FIELD", payload: {
|
||||
sectionId,
|
||||
field: "levelGenerating",
|
||||
value: section!.levelGenerating.filter((g) => g !== `${local.id ? `${local.id}-` : ''}video`),
|
||||
module
|
||||
}
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [levelGenResults, levelGenerating]);
|
||||
|
||||
const addPrompt = () => {
|
||||
setLocal(prev => ({
|
||||
...prev,
|
||||
@@ -143,7 +248,7 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
<>
|
||||
<div className='relative pb-4'>
|
||||
<Header
|
||||
title={`Speaking ${sectionId} Script`}
|
||||
title={`Speaking ${module === "level" ? local.sectionId : sectionId} Script`}
|
||||
description='Generate or write the script for the video.'
|
||||
editing={editing}
|
||||
handleSave={handleSave}
|
||||
@@ -154,7 +259,7 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
module="speaking"
|
||||
/>
|
||||
</div>
|
||||
{generating && generating === "speakingScript" ? (
|
||||
{((generating && generating === "speakingScript") || (levelGenerating.find((g) => g === `${local.id}-speakingScript`))) ? (
|
||||
<GenLoader module={module} />
|
||||
) : (
|
||||
<>
|
||||
@@ -263,14 +368,14 @@ const Speaking2: React.FC<Props> = ({ sectionId, exercise, module = "speaking" }
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 w-full items-center">
|
||||
<video controls className="w-full rounded-xl">
|
||||
<source src={local.video_url } />
|
||||
<source src={local.video_url} />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
}
|
||||
{generating && generating === "video" &&
|
||||
{((generating === "video") || (levelGenerating.find((g) => g === `${local.id}-video`) !== undefined)) &&
|
||||
<GenLoader module={module} custom="Generating the video ... This may take a while ..." />
|
||||
}
|
||||
<Card>
|
||||
|
||||
@@ -8,28 +8,20 @@ import { Module } from "@/interfaces";
|
||||
interface Props {
|
||||
sectionId: number;
|
||||
exercise: SpeakingExercise | InteractiveSpeakingExercise;
|
||||
qId?: number;
|
||||
module: Module;
|
||||
}
|
||||
|
||||
const Speaking: React.FC<Props> = ({ sectionId, exercise, qId, module = "speaking" }) => {
|
||||
const { dispatch } = useExamEditorStore();
|
||||
const Speaking: React.FC<Props> = ({ sectionId, module = "speaking" }) => {
|
||||
const { state } = useExamEditorStore(
|
||||
(state) => state.modules[module].sections.find((section) => section.sectionId === sectionId)!
|
||||
);
|
||||
|
||||
const onFocus = () => {
|
||||
if (qId) {
|
||||
dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { module, sectionId, field: "focusedExercise", value: { questionId: qId, id: exercise.id } } })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div tabIndex={0} className="mx-auto p-3 space-y-6" onFocus={onFocus}>
|
||||
<div className="mx-auto p-3 space-y-6">
|
||||
<div className="p-4">
|
||||
<div className="flex flex-col space-y-6">
|
||||
{sectionId === 1 && <Speaking1 sectionId={sectionId} exercise={state as InteractiveSpeakingExercise} />}
|
||||
{sectionId === 1 && <Speaking1 sectionId={sectionId} exercise={state as InteractiveSpeakingExercise } />}
|
||||
{sectionId === 2 && <Speaking2 sectionId={sectionId} exercise={state as SpeakingExercise} />}
|
||||
{sectionId === 3 && <InteractiveSpeaking sectionId={sectionId} exercise={state as InteractiveSpeakingExercise} />}
|
||||
</div>
|
||||
|
||||
@@ -100,7 +100,7 @@ const Writing: React.FC<Props> = ({ sectionId, exercise, module, index }) => {
|
||||
description='Generate or edit the instructions for the task'
|
||||
editing={editing}
|
||||
handleSave={handleSave}
|
||||
handleDelete={handleDelete}
|
||||
handleDelete={module == "level" ? handleDelete : undefined}
|
||||
handleEdit={handleEdit}
|
||||
handleDiscard={handleDiscard}
|
||||
handlePractice={handlePractice}
|
||||
|
||||
Reference in New Issue
Block a user