Added the ability to choose a difficulty when generating an exam
This commit is contained in:
@@ -1,23 +1,30 @@
|
|||||||
import {LevelExam, MultipleChoiceExercise} from "@/interfaces/exam";
|
import Select from "@/components/Low/Select";
|
||||||
|
import {Difficulty, LevelExam, MultipleChoiceExercise} from "@/interfaces/exam";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import {getExamById} from "@/utils/exams";
|
import {getExamById} from "@/utils/exams";
|
||||||
import {playSound} from "@/utils/sound";
|
import {playSound} from "@/utils/sound";
|
||||||
import {Tab} from "@headlessui/react";
|
import {Tab} from "@headlessui/react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import {capitalize, sample} from "lodash";
|
||||||
import {useRouter} from "next/router";
|
import {useRouter} from "next/router";
|
||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {BsArrowRepeat} from "react-icons/bs";
|
import {BsArrowRepeat} from "react-icons/bs";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
import {v4} from "uuid";
|
import {v4} from "uuid";
|
||||||
|
|
||||||
const TaskTab = ({exam, setExam}: {exam?: LevelExam; setExam: (exam: LevelExam) => void}) => {
|
const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"];
|
||||||
|
|
||||||
|
const TaskTab = ({exam, difficulty, setExam}: {exam?: LevelExam; difficulty: Difficulty; setExam: (exam: LevelExam) => void}) => {
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const generate = () => {
|
const generate = () => {
|
||||||
|
const url = new URLSearchParams();
|
||||||
|
url.append("difficulty", difficulty);
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
axios
|
axios
|
||||||
.get(`/api/exam/level/generate/level`)
|
.get(`/api/exam/level/generate/level?${url.toString()}`)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
playSound(typeof result.data === "string" ? "error" : "check");
|
playSound(typeof result.data === "string" ? "error" : "check");
|
||||||
if (typeof result.data === "string") return toast.error("Something went wrong, please try to generate again.");
|
if (typeof result.data === "string") return toast.error("Something went wrong, please try to generate again.");
|
||||||
@@ -107,6 +114,7 @@ const LevelGeneration = () => {
|
|||||||
const [generatedExam, setGeneratedExam] = useState<LevelExam>();
|
const [generatedExam, setGeneratedExam] = useState<LevelExam>();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [resultingExam, setResultingExam] = useState<LevelExam>();
|
const [resultingExam, setResultingExam] = useState<LevelExam>();
|
||||||
|
const [difficulty, setDifficulty] = useState<Difficulty>(sample(DIFFICULTIES)!);
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -163,6 +171,16 @@ const LevelGeneration = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className="flex gap-4 w-1/2">
|
||||||
|
<div className="flex flex-col gap-3 w-full">
|
||||||
|
<label className="font-normal text-base text-mti-gray-dim">Difficulty</label>
|
||||||
|
<Select
|
||||||
|
options={DIFFICULTIES.map((x) => ({value: x, label: capitalize(x)}))}
|
||||||
|
onChange={(value) => (value ? setDifficulty(value.value as Difficulty) : null)}
|
||||||
|
value={{value: difficulty, label: capitalize(difficulty)}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-level/20 p-1">
|
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-level/20 p-1">
|
||||||
<Tab
|
<Tab
|
||||||
@@ -178,7 +196,7 @@ const LevelGeneration = () => {
|
|||||||
</Tab>
|
</Tab>
|
||||||
</Tab.List>
|
</Tab.List>
|
||||||
<Tab.Panels>
|
<Tab.Panels>
|
||||||
<TaskTab exam={generatedExam} setExam={setGeneratedExam} />
|
<TaskTab difficulty={difficulty} exam={generatedExam} setExam={setGeneratedExam} />
|
||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
<div className="w-full flex justify-end gap-4">
|
<div className="w-full flex justify-end gap-4">
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Input from "@/components/Low/Input";
|
import Input from "@/components/Low/Input";
|
||||||
import {Exercise, ListeningExam} from "@/interfaces/exam";
|
import Select from "@/components/Low/Select";
|
||||||
|
import {Difficulty, Exercise, ListeningExam} from "@/interfaces/exam";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import {getExamById} from "@/utils/exams";
|
import {getExamById} from "@/utils/exams";
|
||||||
import {playSound} from "@/utils/sound";
|
import {playSound} from "@/utils/sound";
|
||||||
@@ -7,17 +8,34 @@ import {convertCamelCaseToReadable} from "@/utils/string";
|
|||||||
import {Tab} from "@headlessui/react";
|
import {Tab} from "@headlessui/react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import {capitalize, sample} from "lodash";
|
||||||
import {useRouter} from "next/router";
|
import {useRouter} from "next/router";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {BsArrowRepeat, BsCheck} from "react-icons/bs";
|
import {BsArrowRepeat, BsCheck} from "react-icons/bs";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
|
|
||||||
const PartTab = ({part, types, index, setPart}: {part?: ListeningPart; types: string[]; index: number; setPart: (part?: ListeningPart) => void}) => {
|
const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"];
|
||||||
|
|
||||||
|
const PartTab = ({
|
||||||
|
part,
|
||||||
|
types,
|
||||||
|
difficulty,
|
||||||
|
index,
|
||||||
|
setPart,
|
||||||
|
}: {
|
||||||
|
part?: ListeningPart;
|
||||||
|
difficulty: Difficulty;
|
||||||
|
types: string[];
|
||||||
|
index: number;
|
||||||
|
setPart: (part?: ListeningPart) => void;
|
||||||
|
}) => {
|
||||||
const [topic, setTopic] = useState("");
|
const [topic, setTopic] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const generate = () => {
|
const generate = () => {
|
||||||
const url = new URLSearchParams();
|
const url = new URLSearchParams();
|
||||||
|
url.append("difficulty", difficulty);
|
||||||
|
|
||||||
if (topic) url.append("topic", topic);
|
if (topic) url.append("topic", topic);
|
||||||
if (types) types.forEach((t) => url.append("exercises", t));
|
if (types) types.forEach((t) => url.append("exercises", t));
|
||||||
|
|
||||||
@@ -115,6 +133,7 @@ const ListeningGeneration = () => {
|
|||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [resultingExam, setResultingExam] = useState<ListeningExam>();
|
const [resultingExam, setResultingExam] = useState<ListeningExam>();
|
||||||
const [types, setTypes] = useState<string[]>([]);
|
const [types, setTypes] = useState<string[]>([]);
|
||||||
|
const [difficulty, setDifficulty] = useState<Difficulty>(sample(DIFFICULTIES)!);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const part1Timer = part1 ? 5 : 0;
|
const part1Timer = part1 ? 5 : 0;
|
||||||
@@ -148,7 +167,7 @@ const ListeningGeneration = () => {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.post(`/api/exam/listening/generate/listening`, {parts, minTimer})
|
.post(`/api/exam/listening/generate/listening`, {parts, minTimer, difficulty})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
playSound("sent");
|
playSound("sent");
|
||||||
console.log(`Generated Exam ID: ${result.data.id}`);
|
console.log(`Generated Exam ID: ${result.data.id}`);
|
||||||
@@ -159,6 +178,7 @@ const ListeningGeneration = () => {
|
|||||||
setPart2(undefined);
|
setPart2(undefined);
|
||||||
setPart3(undefined);
|
setPart3(undefined);
|
||||||
setPart4(undefined);
|
setPart4(undefined);
|
||||||
|
setDifficulty(sample(DIFFICULTIES)!);
|
||||||
setTypes([]);
|
setTypes([]);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -186,6 +206,7 @@ const ListeningGeneration = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className="flex gap-4 w-1/2">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<label className="font-normal text-base text-mti-gray-dim">Timer</label>
|
<label className="font-normal text-base text-mti-gray-dim">Timer</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -196,6 +217,16 @@ const ListeningGeneration = () => {
|
|||||||
className="max-w-[300px]"
|
className="max-w-[300px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col gap-3 w-full">
|
||||||
|
<label className="font-normal text-base text-mti-gray-dim">Difficulty</label>
|
||||||
|
<Select
|
||||||
|
options={DIFFICULTIES.map((x) => ({value: x, label: capitalize(x)}))}
|
||||||
|
onChange={(value) => (value ? setDifficulty(value.value as Difficulty) : null)}
|
||||||
|
value={{value: difficulty, label: capitalize(difficulty)}}
|
||||||
|
disabled={!!part1 || !!part2 || !!part3 || !!part4}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<label className="font-normal text-base text-mti-gray-dim">Exercises</label>
|
<label className="font-normal text-base text-mti-gray-dim">Exercises</label>
|
||||||
@@ -271,7 +302,7 @@ const ListeningGeneration = () => {
|
|||||||
{part: part3, setPart: setPart3},
|
{part: part3, setPart: setPart3},
|
||||||
{part: part4, setPart: setPart4},
|
{part: part4, setPart: setPart4},
|
||||||
].map(({part, setPart}, index) => (
|
].map(({part, setPart}, index) => (
|
||||||
<PartTab part={part} types={types} index={index + 1} key={index} setPart={setPart} />
|
<PartTab part={part} difficulty={difficulty} types={types} index={index + 1} key={index} setPart={setPart} />
|
||||||
))}
|
))}
|
||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Input from "@/components/Low/Input";
|
import Input from "@/components/Low/Input";
|
||||||
import {ReadingExam, ReadingPart} from "@/interfaces/exam";
|
import Select from "@/components/Low/Select";
|
||||||
|
import {Difficulty, ReadingExam, ReadingPart} from "@/interfaces/exam";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import {getExamById} from "@/utils/exams";
|
import {getExamById} from "@/utils/exams";
|
||||||
import {playSound} from "@/utils/sound";
|
import {playSound} from "@/utils/sound";
|
||||||
@@ -7,18 +8,35 @@ import {convertCamelCaseToReadable} from "@/utils/string";
|
|||||||
import {Tab} from "@headlessui/react";
|
import {Tab} from "@headlessui/react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import {capitalize, sample} from "lodash";
|
||||||
import {useRouter} from "next/router";
|
import {useRouter} from "next/router";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {BsArrowRepeat, BsCheck} from "react-icons/bs";
|
import {BsArrowRepeat, BsCheck} from "react-icons/bs";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
import {v4} from "uuid";
|
import {v4} from "uuid";
|
||||||
|
|
||||||
const PartTab = ({part, types, index, setPart}: {part?: ReadingPart; types: string[]; index: number; setPart: (part?: ReadingPart) => void}) => {
|
const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"];
|
||||||
|
|
||||||
|
const PartTab = ({
|
||||||
|
part,
|
||||||
|
types,
|
||||||
|
difficulty,
|
||||||
|
index,
|
||||||
|
setPart,
|
||||||
|
}: {
|
||||||
|
part?: ReadingPart;
|
||||||
|
types: string[];
|
||||||
|
index: number;
|
||||||
|
difficulty: Difficulty;
|
||||||
|
setPart: (part?: ReadingPart) => void;
|
||||||
|
}) => {
|
||||||
const [topic, setTopic] = useState("");
|
const [topic, setTopic] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const generate = () => {
|
const generate = () => {
|
||||||
const url = new URLSearchParams();
|
const url = new URLSearchParams();
|
||||||
|
url.append("difficulty", difficulty);
|
||||||
|
|
||||||
if (topic) url.append("topic", topic);
|
if (topic) url.append("topic", topic);
|
||||||
if (types) types.forEach((t) => url.append("exercises", t));
|
if (types) types.forEach((t) => url.append("exercises", t));
|
||||||
|
|
||||||
@@ -92,6 +110,7 @@ const ReadingGeneration = () => {
|
|||||||
const [types, setTypes] = useState<string[]>([]);
|
const [types, setTypes] = useState<string[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [resultingExam, setResultingExam] = useState<ReadingExam>();
|
const [resultingExam, setResultingExam] = useState<ReadingExam>();
|
||||||
|
const [difficulty, setDifficulty] = useState<Difficulty>(sample(DIFFICULTIES)!);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const parts = [part1, part2, part3].filter((x) => !!x);
|
const parts = [part1, part2, part3].filter((x) => !!x);
|
||||||
@@ -144,6 +163,7 @@ const ReadingGeneration = () => {
|
|||||||
id: v4(),
|
id: v4(),
|
||||||
type: "academic",
|
type: "academic",
|
||||||
variant: parts.length === 3 ? "full" : "partial",
|
variant: parts.length === 3 ? "full" : "partial",
|
||||||
|
difficulty,
|
||||||
};
|
};
|
||||||
|
|
||||||
axios
|
axios
|
||||||
@@ -157,6 +177,7 @@ const ReadingGeneration = () => {
|
|||||||
setPart1(undefined);
|
setPart1(undefined);
|
||||||
setPart2(undefined);
|
setPart2(undefined);
|
||||||
setPart3(undefined);
|
setPart3(undefined);
|
||||||
|
setDifficulty(sample(DIFFICULTIES)!);
|
||||||
setMinTimer(60);
|
setMinTimer(60);
|
||||||
setTypes([]);
|
setTypes([]);
|
||||||
})
|
})
|
||||||
@@ -169,6 +190,7 @@ const ReadingGeneration = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className="flex gap-4 w-1/2">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<label className="font-normal text-base text-mti-gray-dim">Timer</label>
|
<label className="font-normal text-base text-mti-gray-dim">Timer</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -179,6 +201,16 @@ const ReadingGeneration = () => {
|
|||||||
className="max-w-[300px]"
|
className="max-w-[300px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col gap-3 w-full">
|
||||||
|
<label className="font-normal text-base text-mti-gray-dim">Difficulty</label>
|
||||||
|
<Select
|
||||||
|
options={DIFFICULTIES.map((x) => ({value: x, label: capitalize(x)}))}
|
||||||
|
onChange={(value) => (value ? setDifficulty(value.value as Difficulty) : null)}
|
||||||
|
value={{value: difficulty, label: capitalize(difficulty)}}
|
||||||
|
disabled={!!part1 || !!part2 || !!part3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<label className="font-normal text-base text-mti-gray-dim">Exercises</label>
|
<label className="font-normal text-base text-mti-gray-dim">Exercises</label>
|
||||||
@@ -240,7 +272,7 @@ const ReadingGeneration = () => {
|
|||||||
{part: part2, setPart: setPart2},
|
{part: part2, setPart: setPart2},
|
||||||
{part: part3, setPart: setPart3},
|
{part: part3, setPart: setPart3},
|
||||||
].map(({part, setPart}, index) => (
|
].map(({part, setPart}, index) => (
|
||||||
<PartTab part={part} types={types} index={index + 1} key={index} setPart={setPart} />
|
<PartTab part={part} types={types} difficulty={difficulty} index={index + 1} key={index} setPart={setPart} />
|
||||||
))}
|
))}
|
||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Input from "@/components/Low/Input";
|
import Input from "@/components/Low/Input";
|
||||||
import Select from "@/components/Low/Select";
|
import Select from "@/components/Low/Select";
|
||||||
import {Exercise, InteractiveSpeakingExercise, SpeakingExam, SpeakingExercise} from "@/interfaces/exam";
|
import {Difficulty, Exercise, InteractiveSpeakingExercise, SpeakingExam, SpeakingExercise} from "@/interfaces/exam";
|
||||||
import {AVATARS} from "@/resources/speakingAvatars";
|
import {AVATARS} from "@/resources/speakingAvatars";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import {getExamById} from "@/utils/exams";
|
import {getExamById} from "@/utils/exams";
|
||||||
@@ -17,7 +17,19 @@ import {BsArrowRepeat, BsCheck} from "react-icons/bs";
|
|||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
import {v4} from "uuid";
|
import {v4} from "uuid";
|
||||||
|
|
||||||
const PartTab = ({part, index, setPart}: {part?: SpeakingPart; index: number; setPart: (part?: SpeakingPart) => void}) => {
|
const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"];
|
||||||
|
|
||||||
|
const PartTab = ({
|
||||||
|
part,
|
||||||
|
index,
|
||||||
|
difficulty,
|
||||||
|
setPart,
|
||||||
|
}: {
|
||||||
|
part?: SpeakingPart;
|
||||||
|
difficulty: Difficulty;
|
||||||
|
index: number;
|
||||||
|
setPart: (part?: SpeakingPart) => void;
|
||||||
|
}) => {
|
||||||
const [gender, setGender] = useState<"male" | "female">("male");
|
const [gender, setGender] = useState<"male" | "female">("male");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
@@ -25,8 +37,11 @@ const PartTab = ({part, index, setPart}: {part?: SpeakingPart; index: number; se
|
|||||||
setPart(undefined);
|
setPart(undefined);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const url = new URLSearchParams();
|
||||||
|
url.append("difficulty", difficulty);
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.get(`/api/exam/speaking/generate/speaking_task_${index}`)
|
.get(`/api/exam/speaking/generate/speaking_task_${index}?${url.toString()}`)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
playSound(typeof result.data === "string" ? "error" : "check");
|
playSound(typeof result.data === "string" ? "error" : "check");
|
||||||
if (typeof result.data === "string") return toast.error("Something went wrong, please try to generate again.");
|
if (typeof result.data === "string") return toast.error("Something went wrong, please try to generate again.");
|
||||||
@@ -174,6 +189,7 @@ const SpeakingGeneration = () => {
|
|||||||
const [minTimer, setMinTimer] = useState(14);
|
const [minTimer, setMinTimer] = useState(14);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [resultingExam, setResultingExam] = useState<SpeakingExam>();
|
const [resultingExam, setResultingExam] = useState<SpeakingExam>();
|
||||||
|
const [difficulty, setDifficulty] = useState<Difficulty>(sample(DIFFICULTIES)!);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const parts = [part1, part2, part3].filter((x) => !!x);
|
const parts = [part1, part2, part3].filter((x) => !!x);
|
||||||
@@ -213,6 +229,7 @@ const SpeakingGeneration = () => {
|
|||||||
setPart1(undefined);
|
setPart1(undefined);
|
||||||
setPart2(undefined);
|
setPart2(undefined);
|
||||||
setPart3(undefined);
|
setPart3(undefined);
|
||||||
|
setDifficulty(sample(DIFFICULTIES)!);
|
||||||
setMinTimer(14);
|
setMinTimer(14);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -240,6 +257,7 @@ const SpeakingGeneration = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className="flex gap-4 w-1/2">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<label className="font-normal text-base text-mti-gray-dim">Timer</label>
|
<label className="font-normal text-base text-mti-gray-dim">Timer</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -250,6 +268,16 @@ const SpeakingGeneration = () => {
|
|||||||
className="max-w-[300px]"
|
className="max-w-[300px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col gap-3 w-full">
|
||||||
|
<label className="font-normal text-base text-mti-gray-dim">Difficulty</label>
|
||||||
|
<Select
|
||||||
|
options={DIFFICULTIES.map((x) => ({value: x, label: capitalize(x)}))}
|
||||||
|
onChange={(value) => (value ? setDifficulty(value.value as Difficulty) : null)}
|
||||||
|
value={{value: difficulty, label: capitalize(difficulty)}}
|
||||||
|
disabled={!!part1 || !!part2 || !!part3}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-speaking/20 p-1">
|
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-speaking/20 p-1">
|
||||||
@@ -293,7 +321,7 @@ const SpeakingGeneration = () => {
|
|||||||
{part: part2, setPart: setPart2},
|
{part: part2, setPart: setPart2},
|
||||||
{part: part3, setPart: setPart3},
|
{part: part3, setPart: setPart3},
|
||||||
].map(({part, setPart}, index) => (
|
].map(({part, setPart}, index) => (
|
||||||
<PartTab part={part} index={index + 1} key={index} setPart={setPart} />
|
<PartTab difficulty={difficulty} part={part} index={index + 1} key={index} setPart={setPart} />
|
||||||
))}
|
))}
|
||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
|
|||||||
@@ -1,24 +1,32 @@
|
|||||||
import Input from "@/components/Low/Input";
|
import Input from "@/components/Low/Input";
|
||||||
import {WritingExam, WritingExercise} from "@/interfaces/exam";
|
import Select from "@/components/Low/Select";
|
||||||
|
import {Difficulty, WritingExam, WritingExercise} from "@/interfaces/exam";
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import {getExamById} from "@/utils/exams";
|
import {getExamById} from "@/utils/exams";
|
||||||
import {playSound} from "@/utils/sound";
|
import {playSound} from "@/utils/sound";
|
||||||
import {Tab} from "@headlessui/react";
|
import {Tab} from "@headlessui/react";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import {capitalize, sample} from "lodash";
|
||||||
import {useRouter} from "next/router";
|
import {useRouter} from "next/router";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {BsArrowRepeat, BsCheck} from "react-icons/bs";
|
import {BsArrowRepeat, BsCheck} from "react-icons/bs";
|
||||||
import {toast} from "react-toastify";
|
import {toast} from "react-toastify";
|
||||||
import {v4} from "uuid";
|
import {v4} from "uuid";
|
||||||
|
|
||||||
const TaskTab = ({task, index, setTask}: {task?: string; index: number; setTask: (task: string) => void}) => {
|
const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"];
|
||||||
|
|
||||||
|
const TaskTab = ({task, index, difficulty, setTask}: {task?: string; difficulty: Difficulty; index: number; setTask: (task: string) => void}) => {
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const generate = () => {
|
const generate = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const url = new URLSearchParams();
|
||||||
|
url.append("difficulty", difficulty);
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.get(`/api/exam/writing/generate/writing_task${index}_general`)
|
.get(`/api/exam/writing/generate/writing_task${index}_general?${url.toString()}`)
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
playSound(typeof result.data === "string" ? "error" : "check");
|
playSound(typeof result.data === "string" ? "error" : "check");
|
||||||
if (typeof result.data === "string") return toast.error("Something went wrong, please try to generate again.");
|
if (typeof result.data === "string") return toast.error("Something went wrong, please try to generate again.");
|
||||||
@@ -72,6 +80,7 @@ const WritingGeneration = () => {
|
|||||||
const [minTimer, setMinTimer] = useState(60);
|
const [minTimer, setMinTimer] = useState(60);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [resultingExam, setResultingExam] = useState<WritingExam>();
|
const [resultingExam, setResultingExam] = useState<WritingExam>();
|
||||||
|
const [difficulty, setDifficulty] = useState<Difficulty>(sample(DIFFICULTIES)!);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const task1Timer = task1 ? 20 : 0;
|
const task1Timer = task1 ? 20 : 0;
|
||||||
@@ -144,6 +153,7 @@ const WritingGeneration = () => {
|
|||||||
exercises: [...(exercise1 ? [exercise1] : []), ...(exercise2 ? [exercise2] : [])],
|
exercises: [...(exercise1 ? [exercise1] : []), ...(exercise2 ? [exercise2] : [])],
|
||||||
id: v4(),
|
id: v4(),
|
||||||
variant: exercise1 && exercise2 ? "full" : "partial",
|
variant: exercise1 && exercise2 ? "full" : "partial",
|
||||||
|
difficulty,
|
||||||
};
|
};
|
||||||
|
|
||||||
axios
|
axios
|
||||||
@@ -156,6 +166,7 @@ const WritingGeneration = () => {
|
|||||||
|
|
||||||
setTask1(undefined);
|
setTask1(undefined);
|
||||||
setTask2(undefined);
|
setTask2(undefined);
|
||||||
|
setDifficulty(sample(DIFFICULTIES)!);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -166,6 +177,7 @@ const WritingGeneration = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div className="flex gap-4 w-1/2">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<label className="font-normal text-base text-mti-gray-dim">Timer</label>
|
<label className="font-normal text-base text-mti-gray-dim">Timer</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -176,6 +188,16 @@ const WritingGeneration = () => {
|
|||||||
className="max-w-[300px]"
|
className="max-w-[300px]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col gap-3 w-full">
|
||||||
|
<label className="font-normal text-base text-mti-gray-dim">Difficulty</label>
|
||||||
|
<Select
|
||||||
|
options={DIFFICULTIES.map((x) => ({value: x, label: capitalize(x)}))}
|
||||||
|
onChange={(value) => (value ? setDifficulty(value.value as Difficulty) : null)}
|
||||||
|
value={{value: difficulty, label: capitalize(difficulty)}}
|
||||||
|
disabled={!!task1 || !!task2}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Tab.Group>
|
<Tab.Group>
|
||||||
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-writing/20 p-1">
|
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-writing/20 p-1">
|
||||||
@@ -207,7 +229,7 @@ const WritingGeneration = () => {
|
|||||||
{task: task1, setTask: setTask1},
|
{task: task1, setTask: setTask1},
|
||||||
{task: task2, setTask: setTask2},
|
{task: task2, setTask: setTask2},
|
||||||
].map(({task, setTask}, index) => (
|
].map(({task, setTask}, index) => (
|
||||||
<TaskTab task={task} index={index + 1} key={index} setTask={setTask} />
|
<TaskTab difficulty={difficulty} task={task} index={index + 1} key={index} setTask={setTask} />
|
||||||
))}
|
))}
|
||||||
</Tab.Panels>
|
</Tab.Panels>
|
||||||
</Tab.Group>
|
</Tab.Group>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {getFirestore, collection, getDocs, query, where} from "firebase/firestor
|
|||||||
import {withIronSessionApiRoute} from "iron-session/next";
|
import {withIronSessionApiRoute} from "iron-session/next";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import {shuffle} from "lodash";
|
import {shuffle} from "lodash";
|
||||||
import {Exam} from "@/interfaces/exam";
|
import {Difficulty, Exam} from "@/interfaces/exam";
|
||||||
import {Stat} from "@/interfaces/user";
|
import {Stat} from "@/interfaces/user";
|
||||||
import {Module} from "@/interfaces";
|
import {Module} from "@/interfaces";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@@ -25,10 +25,21 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
if (!req.session.user) return res.status(401).json({ok: false});
|
if (!req.session.user) return res.status(401).json({ok: false});
|
||||||
if (req.session.user.type !== "developer") return res.status(403).json({ok: false});
|
if (req.session.user.type !== "developer") return res.status(403).json({ok: false});
|
||||||
|
|
||||||
const {endpoint, topic, exercises} = req.query as {module: Module; endpoint: string; topic?: string; exercises?: string[]};
|
const {endpoint, topic, exercises, difficulty} = req.query as {
|
||||||
|
module: Module;
|
||||||
|
endpoint: string;
|
||||||
|
topic?: string;
|
||||||
|
exercises?: string[];
|
||||||
|
difficulty?: Difficulty;
|
||||||
|
};
|
||||||
const url = `${process.env.BACKEND_URL}/${endpoint}`;
|
const url = `${process.env.BACKEND_URL}/${endpoint}`;
|
||||||
|
|
||||||
const result = await axios.get(`${url}${topic && exercises ? `?topic=${topic.toLowerCase()}&exercises=${exercises.join("&exercises=")}` : ""}`, {
|
const params = new URLSearchParams();
|
||||||
|
if (topic) params.append("topic", topic);
|
||||||
|
if (exercises) exercises.forEach((exercise) => params.append("exercises", exercise));
|
||||||
|
if (difficulty) params.append("difficulty", difficulty);
|
||||||
|
|
||||||
|
const result = await axios.get(`${url}${params.toString().length > 0 ? `?${params.toString()}` : ""}`, {
|
||||||
headers: {Authorization: `Bearer ${process.env.BACKEND_JWT}`},
|
headers: {Authorization: `Bearer ${process.env.BACKEND_JWT}`},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user