From 21e58e3b9c4642e535949d42568f95aab226d4a9 Mon Sep 17 00:00:00 2001 From: Carlos Mesquita Date: Wed, 7 Aug 2024 13:04:42 +0100 Subject: [PATCH] Tooltips for assessment criteria on Writing and Speaking --- .../Solutions/InteractiveSpeaking.tsx | 51 ++++++++++-------- src/components/Solutions/Speaking.tsx | 49 ++++++++++------- src/components/Solutions/Writing.tsx | 52 +++++++++++-------- 3 files changed, 90 insertions(+), 62 deletions(-) diff --git a/src/components/Solutions/InteractiveSpeaking.tsx b/src/components/Solutions/InteractiveSpeaking.tsx index 6038e1a9..ad906c0f 100644 --- a/src/components/Solutions/InteractiveSpeaking.tsx +++ b/src/components/Solutions/InteractiveSpeaking.tsx @@ -1,17 +1,17 @@ /* eslint-disable @next/next/no-img-element */ -import {InteractiveSpeakingExercise} from "@/interfaces/exam"; -import {CommonProps} from "."; -import {useEffect, useState} from "react"; +import { InteractiveSpeakingExercise } from "@/interfaces/exam"; +import { CommonProps } from "."; +import { useEffect, useState } from "react"; import Button from "../Low/Button"; import dynamic from "next/dynamic"; import axios from "axios"; -import {speakingReverseMarking} from "@/utils/score"; -import {Tab} from "@headlessui/react"; +import { speakingReverseMarking } from "@/utils/score"; +import { Tab } from "@headlessui/react"; import clsx from "clsx"; import Modal from "../Modal"; -import ReactDiffViewer, {DiffMethod} from "react-diff-viewer"; +import ReactDiffViewer, { DiffMethod } from "react-diff-viewer"; -const Waveform = dynamic(() => import("../Waveform"), {ssr: false}); +const Waveform = dynamic(() => import("../Waveform"), { ssr: false }); export default function InteractiveSpeaking({ id, @@ -26,13 +26,20 @@ export default function InteractiveSpeaking({ const [solutionsURL, setSolutionsURL] = useState([]); const [diffNumber, setDiffNumber] = useState(0); + const tooltips: { [key: string]: string } = { + "Grammatical Range and Accuracy": "Assesses the variety and correctness of grammatical structures used. A higher score indicates a wide range of complex and accurate grammar; a lower score suggests the need for more basic grammar practice.", + "Fluency and Coherence": "Evaluates smoothness and logical flow of speech. A higher score means natural, effortless speech and clear idea progression; a lower score indicates frequent pauses and difficulty in maintaining coherence.", + "Pronunciation": "Measures clarity and accuracy of spoken words. A higher score reflects clear, well-articulated speech with correct intonation; a lower score shows challenges in being understood.", + "Lexical Resource": "Looks at the range and appropriateness of vocabulary. A higher score demonstrates a rich and precise vocabulary; a lower score suggests limited vocabulary usage and appropriateness.", + }; + useEffect(() => { if (userSolutions && userSolutions.length > 0 && userSolutions[0].solution) { - Promise.all(userSolutions[0].solution.map((x) => axios.post(`/api/speaking`, {path: x.answer}, {responseType: "arraybuffer"}))).then( + Promise.all(userSolutions[0].solution.map((x) => axios.post(`/api/speaking`, { path: x.answer }, { responseType: "arraybuffer" }))).then( (values) => { setSolutionsURL( - values.map(({data}) => { - const blob = new Blob([data], {type: "audio/wav"}); + values.map(({ data }) => { + const blob = new Blob([data], { type: "audio/wav" }); const url = URL.createObjectURL(blob); return url; @@ -64,13 +71,13 @@ export default function InteractiveSpeaking({ fontFamily: '"Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif', padding: "32px 28px", }, - marker: {display: "none"}, - diffRemoved: {padding: "32px 28px"}, - diffAdded: {padding: "32px 28px"}, + marker: { display: "none" }, + diffRemoved: { padding: "32px 28px" }, + diffAdded: { padding: "32px 28px" }, - wordRemoved: {padding: "0px", display: "initial"}, - wordAdded: {padding: "0px", display: "initial"}, - wordDiff: {padding: "0px", display: "initial"}, + wordRemoved: { padding: "0px", display: "initial" }, + wordAdded: { padding: "0px", display: "initial" }, + wordDiff: { padding: "0px", display: "initial" }, }} oldValue={userSolutions[0].evaluation[`transcript_${diffNumber}`]?.replaceAll("\\n", "\n")} newValue={userSolutions[0].evaluation[`fixed_text_${diffNumber}`]?.replaceAll("\\n", "\n")} @@ -132,12 +139,14 @@ export default function InteractiveSpeaking({ {userSolutions && userSolutions.length > 0 && userSolutions[0].evaluation && typeof userSolutions[0].evaluation !== "string" && (
- {Object.keys(userSolutions[0].evaluation!.task_response).map((key) => { + {Object.keys(userSolutions[0].evaluation!.task_response).map((key, index) => { const taskResponse = userSolutions[0].evaluation!.task_response[key]; const grade: number = typeof taskResponse === "number" ? taskResponse : taskResponse.grade; return ( -
+
{key}: Level {grade}
); @@ -148,7 +157,7 @@ export default function InteractiveSpeaking({ + className={({ selected }) => clsx( "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80", "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2", @@ -159,7 +168,7 @@ export default function InteractiveSpeaking({ General Feedback + className={({ selected }) => clsx( "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80", "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2", @@ -232,7 +241,7 @@ export default function InteractiveSpeaking({ onBack({ exercise: id, solutions: userSolutions, - score: {total: 100, missing: 0, correct: speakingReverseMarking[userSolutions[0]!.evaluation!.overall] || 0}, + score: { total: 100, missing: 0, correct: speakingReverseMarking[userSolutions[0]!.evaluation!.overall] || 0 }, type, }) } diff --git a/src/components/Solutions/Speaking.tsx b/src/components/Solutions/Speaking.tsx index 90501894..82c76f46 100644 --- a/src/components/Solutions/Speaking.tsx +++ b/src/components/Solutions/Speaking.tsx @@ -1,20 +1,20 @@ /* eslint-disable @next/next/no-img-element */ -import {SpeakingExercise} from "@/interfaces/exam"; -import {CommonProps} from "."; -import {Fragment, useEffect, useState} from "react"; +import { SpeakingExercise } from "@/interfaces/exam"; +import { CommonProps } from "."; +import { Fragment, useEffect, useState } from "react"; import Button from "../Low/Button"; import dynamic from "next/dynamic"; import axios from "axios"; -import {speakingReverseMarking} from "@/utils/score"; -import {Tab} from "@headlessui/react"; +import { speakingReverseMarking } from "@/utils/score"; +import { Tab } from "@headlessui/react"; import clsx from "clsx"; import Modal from "../Modal"; -import {BsQuestionCircleFill} from "react-icons/bs"; -import ReactDiffViewer, {DiffMethod} from "react-diff-viewer"; +import { BsQuestionCircleFill } from "react-icons/bs"; +import ReactDiffViewer, { DiffMethod } from "react-diff-viewer"; -const Waveform = dynamic(() => import("../Waveform"), {ssr: false}); +const Waveform = dynamic(() => import("../Waveform"), { ssr: false }); -export default function Speaking({id, type, title, video_url, text, prompts, userSolutions, onNext, onBack}: SpeakingExercise & CommonProps) { +export default function Speaking({ id, type, title, video_url, text, prompts, userSolutions, onNext, onBack }: SpeakingExercise & CommonProps) { const [solutionURL, setSolutionURL] = useState(); const [showDiff, setShowDiff] = useState(false); @@ -23,8 +23,8 @@ export default function Speaking({id, type, title, video_url, text, prompts, use const solution = userSolutions[0].solution; if (solution.startsWith("https://")) return setSolutionURL(solution); - axios.post(`/api/speaking`, {path: userSolutions[0].solution}, {responseType: "arraybuffer"}).then(({data}) => { - const blob = new Blob([data], {type: "audio/wav"}); + axios.post(`/api/speaking`, { path: userSolutions[0].solution }, { responseType: "arraybuffer" }).then(({ data }) => { + const blob = new Blob([data], { type: "audio/wav" }); const url = URL.createObjectURL(blob); setSolutionURL(url); @@ -32,6 +32,13 @@ export default function Speaking({id, type, title, video_url, text, prompts, use } }, [userSolutions]); + const tooltips: { [key: string]: string } = { + "Grammatical Range and Accuracy": "Assesses the variety and correctness of grammatical structures used. A higher score indicates a wide range of complex and accurate grammar; a lower score suggests the need for more basic grammar practice.", + "Fluency and Coherence": "Evaluates smoothness and logical flow of speech. A higher score means natural, effortless speech and clear idea progression; a lower score indicates frequent pauses and difficulty in maintaining coherence.", + "Pronunciation": "Measures clarity and accuracy of spoken words. A higher score reflects clear, well-articulated speech with correct intonation; a lower score shows challenges in being understood.", + "Lexical Resource": "Looks at the range and appropriateness of vocabulary. A higher score demonstrates a rich and precise vocabulary; a lower score suggests limited vocabulary usage and appropriateness.", + }; + return ( <> setShowDiff(false)}> @@ -51,13 +58,13 @@ export default function Speaking({id, type, title, video_url, text, prompts, use fontFamily: '"Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif', padding: "32px 28px", }, - marker: {display: "none"}, - diffRemoved: {padding: "32px 28px"}, - diffAdded: {padding: "32px 28px"}, + marker: { display: "none" }, + diffRemoved: { padding: "32px 28px" }, + diffAdded: { padding: "32px 28px" }, - wordRemoved: {padding: "0px", display: "initial"}, - wordAdded: {padding: "0px", display: "initial"}, - wordDiff: {padding: "0px", display: "initial"}, + wordRemoved: { padding: "0px", display: "initial" }, + wordAdded: { padding: "0px", display: "initial" }, + wordDiff: { padding: "0px", display: "initial" }, }} oldValue={userSolutions[0].evaluation.transcript_1.replaceAll("\\n", "\n")} newValue={userSolutions[0].evaluation.fixed_text_1.replaceAll("\\n", "\n")} @@ -126,12 +133,14 @@ export default function Speaking({id, type, title, video_url, text, prompts, use {userSolutions && userSolutions.length > 0 && userSolutions[0].evaluation && typeof userSolutions[0].evaluation !== "string" && (
- {Object.keys(userSolutions[0].evaluation!.task_response).map((key) => { + {Object.keys(userSolutions[0].evaluation!.task_response).map((key, index) => { const taskResponse = userSolutions[0].evaluation!.task_response[key]; const grade: number = typeof taskResponse === "number" ? taskResponse : taskResponse.grade; return ( -
+
{key}: Level {grade}
); @@ -142,7 +151,7 @@ export default function Speaking({id, type, title, video_url, text, prompts, use + className={({ selected }) => clsx( "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/80", "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking focus:outline-none focus:ring-2", diff --git a/src/components/Solutions/Writing.tsx b/src/components/Solutions/Writing.tsx index 37eea2cf..6f853da1 100644 --- a/src/components/Solutions/Writing.tsx +++ b/src/components/Solutions/Writing.tsx @@ -1,23 +1,30 @@ /* eslint-disable @next/next/no-img-element */ -import {WritingExercise} from "@/interfaces/exam"; -import {CommonProps} from "."; -import {Fragment, useEffect, useState} from "react"; +import { WritingExercise } from "@/interfaces/exam"; +import { CommonProps } from "."; +import { Fragment, useEffect, useState } from "react"; import Button from "../Low/Button"; -import {Dialog, Tab, Transition} from "@headlessui/react"; -import {writingReverseMarking} from "@/utils/score"; +import { Dialog, Tab, Transition } from "@headlessui/react"; +import { writingReverseMarking } from "@/utils/score"; import clsx from "clsx"; -import ReactDiffViewer, {DiffMethod} from "react-diff-viewer"; +import ReactDiffViewer, { DiffMethod } from "react-diff-viewer"; import useUser from "@/hooks/useUser"; import AIDetection from "../AIDetection"; -export default function Writing({id, type, prompt, attachment, userSolutions, onNext, onBack}: WritingExercise & CommonProps) { +export default function Writing({ id, type, prompt, attachment, userSolutions, onNext, onBack }: WritingExercise & CommonProps) { const [isModalOpen, setIsModalOpen] = useState(false); const [showDiff, setShowDiff] = useState(false); - const {user} = useUser(); + const { user } = useUser(); const aiEval = userSolutions && userSolutions.length > 0 ? userSolutions[0].evaluation?.ai_detection : undefined; + const tooltips: { [key: string]: string } = { + "Lexical Resource": "Assesses the diversity and accuracy of vocabulary used. A higher score indicates varied and precise word choice; a lower score points to limited vocabulary and inaccuracies.", + "Task Achievement": "Evaluates how well the task requirements are fulfilled. A higher score means all parts of the task are addressed thoroughly; a lower score shows incomplete or inadequate task response.", + "Coherence and Cohesion": "Measures logical organization and flow of writing. A higher score reflects well-structured and connected ideas; a lower score indicates disorganized writing and poor linkage between ideas.", + "Grammatical Range and Accuracy": "Looks at the range and precision of grammatical structures. A higher score shows varied and accurate grammar use; a lower score suggests frequent errors and limited range.", + }; + return ( <> {attachment && ( @@ -92,13 +99,13 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on fontFamily: '"Open Sans", system-ui, -apple-system, "Helvetica Neue", sans-serif', padding: "32px 28px", }, - marker: {display: "none"}, - diffRemoved: {padding: "32px 28px"}, - diffAdded: {padding: "32px 28px"}, + marker: { display: "none" }, + diffRemoved: { padding: "32px 28px" }, + diffAdded: { padding: "32px 28px" }, - wordRemoved: {padding: "0px", display: "initial"}, - wordAdded: {padding: "0px", display: "initial"}, - wordDiff: {padding: "0px", display: "initial"}, + wordRemoved: { padding: "0px", display: "initial" }, + wordAdded: { padding: "0px", display: "initial" }, + wordDiff: { padding: "0px", display: "initial" }, }} oldValue={userSolutions[0].solution.replaceAll("\\n", "\n")} newValue={userSolutions[0].evaluation!.fixed_text!.replaceAll("\\n", "\n")} @@ -123,12 +130,15 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on {userSolutions && userSolutions.length > 0 && userSolutions[0].evaluation && typeof userSolutions[0].evaluation !== "string" && (
- {Object.keys(userSolutions[0].evaluation!.task_response).map((key) => { + {Object.keys(userSolutions[0].evaluation!.task_response).map((key, index) => { const taskResponse = userSolutions[0].evaluation!.task_response[key]; const grade: number = typeof taskResponse === "number" ? taskResponse : taskResponse.grade; return ( -
+
{key}: Level {grade}
); @@ -138,7 +148,7 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on + className={({ selected }) => clsx( "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-writing/80", "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-writing focus:outline-none focus:ring-2", @@ -149,7 +159,7 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on General Feedback + className={({ selected }) => clsx( "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-writing/80", "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-writing focus:outline-none focus:ring-2", @@ -160,7 +170,7 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on Evaluation + className={({ selected }) => clsx( "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-writing/80", "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-writing focus:outline-none focus:ring-2", @@ -172,7 +182,7 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on {aiEval && user?.type !== "student" && ( + className={({ selected }) => clsx( "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-writing/80", "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-writing focus:outline-none focus:ring-2", @@ -238,7 +248,7 @@ export default function Writing({id, type, prompt, attachment, userSolutions, on onBack({ exercise: id, solutions: userSolutions, - score: {total: 100, missing: 0, correct: writingReverseMarking[userSolutions[0]!.evaluation!.overall] || 0}, + score: { total: 100, missing: 0, correct: writingReverseMarking[userSolutions[0]!.evaluation!.overall] || 0 }, type, }) }