@nualang/nualang-ui-components 0.1.1412 → 0.1.1413
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Dialogs/QuickCreateChatbotDialog/QuickCreateChatbotDialog.js +146 -34
- package/dist/Dialogs/QuickCreateScenarioDialog/QuickCreateScenarioDialog.js +0 -1
- package/dist/Exercises/ListenAndSpeak/ListenAndSpeak.js +1 -18
- package/dist/Exercises/Meaning/Meaning.js +1 -18
- package/dist/Exercises/Translator/Translator.js +27 -7
- package/dist/Forms/VoiceSelector/VoiceSelector.js +713 -296
- package/dist/Forms/VoiceSelector/VoiceSelector.test.js +247 -4
- package/dist/Lists/Exercises/Exercises.js +6 -2
- package/dist/Misc/NotFound/ClassCourses.js +2 -1
- package/dist/Misc/NotFound/Courses.js +2 -1
- package/dist/hooks/useExerciseState.js +92 -26
- package/dist/utils/voices.js +26 -0
- package/package.json +1 -1
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { useState, useEffect, forwardRef,
|
|
2
|
-
import { TextField, MenuItem, ListSubheader, Box, Paper, Typography } from "@mui/material";
|
|
3
|
-
import { ForumRounded, QuizRounded, ImageRounded, SentimentSatisfiedRounded, CheckRounded } from "@mui/icons-material";
|
|
1
|
+
import { useState, useEffect, forwardRef, createElement } from "react";
|
|
2
|
+
import { TextField, MenuItem, ListSubheader, Box, Paper, Typography, Grid, Button, Avatar } from "@mui/material";
|
|
3
|
+
import { ForumRounded, QuizRounded, ImageRounded, SentimentSatisfiedRounded, CheckRounded, Person } from "@mui/icons-material";
|
|
4
4
|
import { useTheme, alpha } from "@mui/material/styles";
|
|
5
5
|
import { keyframes } from "@emotion/react";
|
|
6
6
|
import ResponsiveDialog from "../ResponsiveDialog/ResponsiveDialog";
|
|
7
7
|
import ChatbotNualaAnimation from "../../Misc/QuickCreateNualaAnimations/ChatbotNualaAnimation";
|
|
8
|
+
import VoiceSelector from "../../Forms/VoiceSelector/VoiceSelector";
|
|
9
|
+
import { findVoice, pickRandomVoice } from "../../utils/voices";
|
|
10
|
+
import { makeStyles } from "tss-react/mui";
|
|
11
|
+
import AvatarSelector from "../../Misc/AvatarSelector/AvatarSelector";
|
|
8
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
13
|
export const BOT_NAME_MAX_LENGTH = 100;
|
|
10
14
|
const peekAnimation = keyframes`
|
|
@@ -14,6 +18,18 @@ const peekAnimation = keyframes`
|
|
|
14
18
|
67% { transform: translateX(-150px) rotate(75deg); }
|
|
15
19
|
100% { transform: translateX(-150px) rotate(75deg); }
|
|
16
20
|
`;
|
|
21
|
+
const useStyles = makeStyles()(theme => ({
|
|
22
|
+
avatar: {
|
|
23
|
+
width: 70,
|
|
24
|
+
height: 70,
|
|
25
|
+
marginBottom: theme.spacing(2),
|
|
26
|
+
cursor: "pointer"
|
|
27
|
+
},
|
|
28
|
+
avatarIcon: {
|
|
29
|
+
width: "80%",
|
|
30
|
+
height: "80%"
|
|
31
|
+
}
|
|
32
|
+
}));
|
|
17
33
|
const ChatbotDialogPaper = /*#__PURE__*/forwardRef(function ChatbotDialogPaper({
|
|
18
34
|
children,
|
|
19
35
|
className,
|
|
@@ -378,7 +394,11 @@ export default function QuickCreateChatbotDialog({
|
|
|
378
394
|
topicDifficulty,
|
|
379
395
|
topicPicture,
|
|
380
396
|
defaultNumberOfQuestions = 5,
|
|
381
|
-
voices = []
|
|
397
|
+
voices = [],
|
|
398
|
+
handleSpeak,
|
|
399
|
+
isUserInternal = false,
|
|
400
|
+
avatars,
|
|
401
|
+
learnLang
|
|
382
402
|
}) {
|
|
383
403
|
const [botTitle, setBotTitle] = useState("");
|
|
384
404
|
const [difficulty, setDifficulty] = useState(topicDifficulty || "");
|
|
@@ -387,6 +407,14 @@ export default function QuickCreateChatbotDialog({
|
|
|
387
407
|
const [riveFile, setRiveFile] = useState("");
|
|
388
408
|
const [numberOfQuestions, setNumberOfQuestions] = useState(defaultNumberOfQuestions);
|
|
389
409
|
const [isLoading, setIsLoading] = useState(false);
|
|
410
|
+
const [selectedVoice, setSelectedVoice] = useState(null);
|
|
411
|
+
const [selectedVoiceLanguageCode, setSelectedVoiceLanguageCode] = useState(null);
|
|
412
|
+
const [selectedVoicePitch, setSelectedVoicePitch] = useState(null);
|
|
413
|
+
const [botPicture, setBotPicture] = useState("");
|
|
414
|
+
const [avatarSelector, setAvatarSelector] = useState(false);
|
|
415
|
+
const {
|
|
416
|
+
classes
|
|
417
|
+
} = useStyles();
|
|
390
418
|
|
|
391
419
|
// Keep the questions field in sync if the default changes while closed.
|
|
392
420
|
useEffect(() => {
|
|
@@ -408,31 +436,31 @@ export default function QuickCreateChatbotDialog({
|
|
|
408
436
|
setTemplateId("");
|
|
409
437
|
setNumberOfQuestions(defaultNumberOfQuestions);
|
|
410
438
|
handleClose();
|
|
439
|
+
// Cleared so the next open seeds a fresh random voice.
|
|
440
|
+
setSelectedVoice(null);
|
|
441
|
+
setSelectedVoiceLanguageCode(null);
|
|
442
|
+
setSelectedVoicePitch(null);
|
|
443
|
+
setBotPicture("");
|
|
444
|
+
setAvatarSelector(false);
|
|
445
|
+
};
|
|
446
|
+
const handleAvatarSelection = value => {
|
|
447
|
+
setBotPicture(value);
|
|
448
|
+
setAvatarSelector(false);
|
|
411
449
|
};
|
|
412
|
-
const voicesArray = useMemo(() => {
|
|
413
|
-
return Object.entries(voices).flatMap(([key, dialect]) => {
|
|
414
|
-
if (!dialect?.voiceOptions || !Array.isArray(dialect.voiceOptions)) {
|
|
415
|
-
return [];
|
|
416
|
-
}
|
|
417
|
-
return dialect.voiceOptions.filter(voice => voice?.name) // Validate required properties
|
|
418
|
-
.map(voice => ({
|
|
419
|
-
voice,
|
|
420
|
-
dialect,
|
|
421
|
-
key
|
|
422
|
-
}));
|
|
423
|
-
});
|
|
424
|
-
}, [voices]);
|
|
425
450
|
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
voice
|
|
433
|
-
dialect
|
|
434
|
-
|
|
435
|
-
|
|
451
|
+
// ActorForm assigns each actor a random voice on mount; standalone, the same
|
|
452
|
+
// seeding happens here each time the dialog opens with nothing chosen yet.
|
|
453
|
+
useEffect(() => {
|
|
454
|
+
if (!open || selectedVoice) return;
|
|
455
|
+
const picked = pickRandomVoice(voices);
|
|
456
|
+
if (!picked) return;
|
|
457
|
+
setSelectedVoice(picked.voice.name);
|
|
458
|
+
setSelectedVoiceLanguageCode(picked.dialect.languageCode);
|
|
459
|
+
setSelectedVoicePitch(null);
|
|
460
|
+
setBotPicture(picked.voice.userImage || "");
|
|
461
|
+
}, [open, selectedVoice, voices]);
|
|
462
|
+
const selectedVoiceOption = findVoice(voices, selectedVoice)?.voice;
|
|
463
|
+
const hasAvailableVoice = Boolean(selectedVoice && selectedVoiceLanguageCode);
|
|
436
464
|
const isValid = Boolean(botTitle.trim()) && Boolean(difficulty) && Boolean(templateId) && Boolean(template && template.botId === templateId) && Number(numberOfQuestions) > 0 && hasAvailableVoice;
|
|
437
465
|
const handleSubmit = async () => {
|
|
438
466
|
if (!isValid) return;
|
|
@@ -449,10 +477,11 @@ export default function QuickCreateChatbotDialog({
|
|
|
449
477
|
selectedTemplate: templateId,
|
|
450
478
|
numberOfQuestions: Number(numberOfQuestions),
|
|
451
479
|
courseSectionTopicId: `${courseId}|${sectionId}|${topicId}`,
|
|
452
|
-
voice:
|
|
453
|
-
voiceLanguageCode:
|
|
454
|
-
name:
|
|
455
|
-
|
|
480
|
+
voice: selectedVoice,
|
|
481
|
+
voiceLanguageCode: selectedVoiceLanguageCode,
|
|
482
|
+
name: selectedVoice,
|
|
483
|
+
voicePitch: selectedVoicePitch,
|
|
484
|
+
botAvatar: botPicture,
|
|
456
485
|
isChallengeBot: false,
|
|
457
486
|
isDynamicResponsesEnabled: "enable",
|
|
458
487
|
picture: topicPicture || template.picture
|
|
@@ -467,7 +496,7 @@ export default function QuickCreateChatbotDialog({
|
|
|
467
496
|
setIsLoading(false);
|
|
468
497
|
}
|
|
469
498
|
};
|
|
470
|
-
return /*#__PURE__*/
|
|
499
|
+
return /*#__PURE__*/_jsxs(ResponsiveDialog, {
|
|
471
500
|
open: open,
|
|
472
501
|
handleClose: isLoading ? undefined : resetAndClose,
|
|
473
502
|
handleSubmit: handleSubmit,
|
|
@@ -476,7 +505,7 @@ export default function QuickCreateChatbotDialog({
|
|
|
476
505
|
closeText: t("cancel") || "Cancel",
|
|
477
506
|
submitText: t("create") || "Create",
|
|
478
507
|
PaperComponent: ChatbotDialogPaper,
|
|
479
|
-
children: /*#__PURE__*/_jsxs(Box, {
|
|
508
|
+
children: [/*#__PURE__*/_jsxs(Box, {
|
|
480
509
|
display: "flex",
|
|
481
510
|
flexDirection: "column",
|
|
482
511
|
gap: 1,
|
|
@@ -537,7 +566,90 @@ export default function QuickCreateChatbotDialog({
|
|
|
537
566
|
},
|
|
538
567
|
onKeyDown: e => e.key === "Enter" && handleSubmit(),
|
|
539
568
|
margin: "normal"
|
|
569
|
+
}), /*#__PURE__*/_jsxs(Grid, {
|
|
570
|
+
size: 12,
|
|
571
|
+
sx: {
|
|
572
|
+
mt: 1
|
|
573
|
+
},
|
|
574
|
+
children: [botPicture ? /*#__PURE__*/_jsx(Avatar, {
|
|
575
|
+
"aria-label": selectedVoiceOption?.name,
|
|
576
|
+
src: botPicture,
|
|
577
|
+
className: classes.avatar,
|
|
578
|
+
onClick: () => setAvatarSelector(true),
|
|
579
|
+
alt: ""
|
|
580
|
+
}) : /*#__PURE__*/_jsx(Avatar, {
|
|
581
|
+
"aria-label": selectedVoiceOption?.name,
|
|
582
|
+
sx: theme => ({
|
|
583
|
+
backgroundColor: theme.palette.mode === "dark" ? "white" : "",
|
|
584
|
+
color: theme.palette.mode === "dark" ? theme.palette.background.default : ""
|
|
585
|
+
}),
|
|
586
|
+
className: classes.avatar,
|
|
587
|
+
onClick: () => setAvatarSelector(true),
|
|
588
|
+
alt: "",
|
|
589
|
+
children: /*#__PURE__*/_jsx(Person, {
|
|
590
|
+
className: classes.avatarIcon
|
|
591
|
+
})
|
|
592
|
+
}), /*#__PURE__*/_jsx(Button, {
|
|
593
|
+
color: "primary",
|
|
594
|
+
component: "span",
|
|
595
|
+
onClick: () => setAvatarSelector(true),
|
|
596
|
+
children: t("choose_avatar")
|
|
597
|
+
}), /*#__PURE__*/_jsx(VoiceSelector, {
|
|
598
|
+
id: "voiceSelector",
|
|
599
|
+
"data-cy": "voiceSelector",
|
|
600
|
+
name: "voiceSelector",
|
|
601
|
+
label: t("bot_voice"),
|
|
602
|
+
value: selectedVoice || "",
|
|
603
|
+
margin: "normal",
|
|
604
|
+
variant: "outlined",
|
|
605
|
+
fullWidth: true,
|
|
606
|
+
required: learnLang !== "irish",
|
|
607
|
+
t: t,
|
|
608
|
+
voices: voices,
|
|
609
|
+
onChange: e => {
|
|
610
|
+
if (e && e.target && e.target.value) {
|
|
611
|
+
const selectedVoice = e.target.value;
|
|
612
|
+
const languageCode = e.target.languageCode;
|
|
613
|
+
setSelectedVoice(selectedVoice);
|
|
614
|
+
handleSpeak?.({
|
|
615
|
+
text: selectedVoice,
|
|
616
|
+
learnLanguage: learnLang,
|
|
617
|
+
voiceName: selectedVoice,
|
|
618
|
+
languageCode
|
|
619
|
+
});
|
|
620
|
+
setSelectedVoiceLanguageCode(languageCode);
|
|
621
|
+
setSelectedVoicePitch(null);
|
|
622
|
+
setBotPicture(e.target.picture);
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
onVoicePitchChange: e => {
|
|
626
|
+
if (e && e.target) {
|
|
627
|
+
setSelectedVoicePitch(e.target.value);
|
|
628
|
+
handleSpeak?.({
|
|
629
|
+
text: selectedVoiceLanguageCode ? t("voice_pitch_example", {
|
|
630
|
+
name: selectedVoice,
|
|
631
|
+
lng: selectedVoiceLanguageCode?.substring(0, 2)
|
|
632
|
+
}) : selectedVoice,
|
|
633
|
+
learnLanguage: learnLang,
|
|
634
|
+
voiceName: selectedVoice,
|
|
635
|
+
languageCode: selectedVoiceLanguageCode,
|
|
636
|
+
pitch: e.target.value
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
voicePitch: selectedVoicePitch,
|
|
641
|
+
isUserInternal: isUserInternal,
|
|
642
|
+
handleSpeak: handleSpeak,
|
|
643
|
+
learnLang: learnLang
|
|
644
|
+
})]
|
|
540
645
|
})]
|
|
541
|
-
})
|
|
646
|
+
}), /*#__PURE__*/_jsx(AvatarSelector, {
|
|
647
|
+
t: t,
|
|
648
|
+
open: avatarSelector,
|
|
649
|
+
avatars: avatars,
|
|
650
|
+
picture: botPicture,
|
|
651
|
+
handleClose: () => setAvatarSelector(false),
|
|
652
|
+
handleSubmit: value => handleAvatarSelection(value)
|
|
653
|
+
})]
|
|
542
654
|
});
|
|
543
655
|
}
|
|
@@ -322,7 +322,7 @@ function ListenAndSpeak({
|
|
|
322
322
|
checked,
|
|
323
323
|
setChecked,
|
|
324
324
|
setIsAssignmentExercise,
|
|
325
|
-
|
|
325
|
+
definitionText
|
|
326
326
|
} = exerciseState;
|
|
327
327
|
useEffect(() => {
|
|
328
328
|
if (currentPhrase?.phrase) {
|
|
@@ -435,18 +435,6 @@ function ListenAndSpeak({
|
|
|
435
435
|
question = questions[0];
|
|
436
436
|
image = question.image || null;
|
|
437
437
|
}
|
|
438
|
-
const definitionText = (() => {
|
|
439
|
-
if (!question) return "";
|
|
440
|
-
if (question.definitions?.length > 0) {
|
|
441
|
-
const raw = question.definitions[0];
|
|
442
|
-
return typeof raw === "string" ? raw : "";
|
|
443
|
-
}
|
|
444
|
-
if (question.translations?.length > 0) {
|
|
445
|
-
const raw = question.translations[0];
|
|
446
|
-
return typeof raw === "string" ? raw : "";
|
|
447
|
-
}
|
|
448
|
-
return "";
|
|
449
|
-
})();
|
|
450
438
|
const handleMoreTranslations = () => {
|
|
451
439
|
setViewMoreTranslations(true);
|
|
452
440
|
};
|
|
@@ -471,11 +459,6 @@ function ListenAndSpeak({
|
|
|
471
459
|
const goBackToAssignment = () => {
|
|
472
460
|
navigate(`/classrooms/${classroomId}#Assignments`);
|
|
473
461
|
};
|
|
474
|
-
useEffect(() => {
|
|
475
|
-
if (open && definitionText && definitionText !== "" && !isCompletionModalOpen && !isCloseModalOpen && !isInProgressModalOpen && learnLang && learnLang !== "irish" && !muteSound && isTourFinished) {
|
|
476
|
-
speak(definitionText, voice, false, "#currentPhrase", voiceLanguageCode, false, voicePitch);
|
|
477
|
-
}
|
|
478
|
-
}, [currentPhrase, open, isTourFinished]);
|
|
479
462
|
const NualaSpeakingAnimation = (voiceMap.get(voice) || {}).speakingImage || null;
|
|
480
463
|
return /*#__PURE__*/_jsxs(Dialog, {
|
|
481
464
|
fullScreen: true,
|
|
@@ -217,7 +217,7 @@ function Meaning({
|
|
|
217
217
|
handleCloseClicked,
|
|
218
218
|
handleTranslationTourFinished,
|
|
219
219
|
setIsAssignmentExercise,
|
|
220
|
-
|
|
220
|
+
definitionText
|
|
221
221
|
} = exerciseState;
|
|
222
222
|
const [selectedPhrase, setSelectedPhrase] = useState(null);
|
|
223
223
|
const [phraseOptions, setPhraseOptions] = useState([]);
|
|
@@ -353,18 +353,6 @@ function Meaning({
|
|
|
353
353
|
question = questions[0];
|
|
354
354
|
image = question.image || null;
|
|
355
355
|
}
|
|
356
|
-
const definitionText = (() => {
|
|
357
|
-
if (!question) return "";
|
|
358
|
-
if (question.definitions?.length > 0) {
|
|
359
|
-
const raw = question.definitions[0];
|
|
360
|
-
return typeof raw === "string" ? raw : "";
|
|
361
|
-
}
|
|
362
|
-
if (question.translations?.length > 0) {
|
|
363
|
-
const raw = question.translations[0];
|
|
364
|
-
return typeof raw === "string" ? raw : "";
|
|
365
|
-
}
|
|
366
|
-
return "";
|
|
367
|
-
})();
|
|
368
356
|
const {
|
|
369
357
|
voice,
|
|
370
358
|
voiceLanguageCode,
|
|
@@ -410,11 +398,6 @@ function Meaning({
|
|
|
410
398
|
navigate(`/classrooms/${classroomId}#Assignments`);
|
|
411
399
|
};
|
|
412
400
|
const isSubmitDisabled = !selectedPhrase || showResult;
|
|
413
|
-
useEffect(() => {
|
|
414
|
-
if (open && definitionText && definitionText !== "" && !isCompletionModalOpen && !isCloseModalOpen && !isInProgressModalOpen && learnLang && learnLang !== "irish" && !isReadWriteModeStudent && !muteSound && isTourFinished) {
|
|
415
|
-
speak(definitionText, voice, false, "#currentPhrase", voiceLanguageCode, false, voicePitch);
|
|
416
|
-
}
|
|
417
|
-
}, [currentPhrase, open, isTourFinished]);
|
|
418
401
|
const NualaSpeakingAnimation = (voiceMap.get(voice) || {}).speakingImage || null;
|
|
419
402
|
return /*#__PURE__*/_jsxs(Dialog, {
|
|
420
403
|
fullScreen: true,
|
|
@@ -143,18 +143,40 @@ function Transition(props) {
|
|
|
143
143
|
...props
|
|
144
144
|
});
|
|
145
145
|
}
|
|
146
|
+
const getMatchingTranslationWords = (translationWords, translations, removeExtraWhiteSpaces) => {
|
|
147
|
+
if (!translations || !translations.length) return [];
|
|
148
|
+
const splitTranslations = translations.map(translation => removeExtraWhiteSpaces(translation ?? "").split(" "));
|
|
149
|
+
const attemptWords = translationWords.map(word => removeAllSymbols(word.text).toLowerCase());
|
|
150
|
+
const attemptJoined = attemptWords.join(" ");
|
|
151
|
+
const exactMatchIndex = splitTranslations.findIndex(words => words.map(w => removeAllSymbols(w).toLowerCase()).join(" ") === attemptJoined);
|
|
152
|
+
if (exactMatchIndex > -1) {
|
|
153
|
+
return splitTranslations[exactMatchIndex];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// No exact match (attempt is incomplete or wrong) - fall back to whichever
|
|
157
|
+
// translation shares the most words with the attempt at the same
|
|
158
|
+
// position, so feedback is still as helpful as possible.
|
|
159
|
+
let bestIndex = 0;
|
|
160
|
+
let bestScore = -1;
|
|
161
|
+
splitTranslations.forEach((words, translationIndex) => {
|
|
162
|
+
const score = attemptWords.filter((word, wordIndex) => words[wordIndex] && removeAllSymbols(words[wordIndex]).toLowerCase() === word).length;
|
|
163
|
+
if (score > bestScore) {
|
|
164
|
+
bestScore = score;
|
|
165
|
+
bestIndex = translationIndex;
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return splitTranslations[bestIndex];
|
|
169
|
+
};
|
|
146
170
|
const WordBankButton = /*#__PURE__*/forwardRef(({
|
|
147
171
|
word,
|
|
148
172
|
index,
|
|
149
|
-
|
|
150
|
-
removeExtraWhiteSpaces,
|
|
173
|
+
translationWordList,
|
|
151
174
|
translationWords,
|
|
152
175
|
setTranslationWords,
|
|
153
176
|
classes,
|
|
154
177
|
handleSelectInAnswerBox,
|
|
155
178
|
checkAnswer
|
|
156
179
|
}, ref) => {
|
|
157
|
-
const translationWordList = question && question.translations && question.translations.length ? removeExtraWhiteSpaces(question.translations[0]).split(" ") : [];
|
|
158
180
|
const key = `${word.text}${word.id}`;
|
|
159
181
|
const isWordCorrect = translationWordList[index] && removeAllSymbols(word.text) === removeAllSymbols(translationWordList[index]);
|
|
160
182
|
const onButtonClick = () => {
|
|
@@ -495,7 +517,7 @@ function Translator({
|
|
|
495
517
|
question = questions[0];
|
|
496
518
|
image = question.image || null;
|
|
497
519
|
}
|
|
498
|
-
const translationWordList = question && question.translations && question.translations.length ?
|
|
520
|
+
const translationWordList = question && question.translations && question.translations.length ? getMatchingTranslationWords(translationWords, question.translations, removeExtraWhiteSpaces) : [];
|
|
499
521
|
const {
|
|
500
522
|
voice,
|
|
501
523
|
voiceLanguageCode,
|
|
@@ -798,9 +820,7 @@ function Translator({
|
|
|
798
820
|
"data-cy": "word-bank-button",
|
|
799
821
|
word: word,
|
|
800
822
|
index: index,
|
|
801
|
-
|
|
802
|
-
currentPhrase: currentPhrase,
|
|
803
|
-
removeExtraWhiteSpaces: removeExtraWhiteSpaces,
|
|
823
|
+
translationWordList: translationWordList,
|
|
804
824
|
translationWords: translationWords,
|
|
805
825
|
setTranslationWords: setTranslationWords,
|
|
806
826
|
classes: classes,
|