@nualang/nualang-ui-components 0.1.1412 → 0.1.1414

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.
Files changed (58) hide show
  1. package/dist/Assignments/AssignmentCardsList/AssignmentCardsList.js +4 -2
  2. package/dist/Dialogs/CreatePhrase/CreatePhrase.js +3 -1
  3. package/dist/Dialogs/CreatePhraseGroupDialog/CreatePhraseGroupDialog.js +4 -2
  4. package/dist/Dialogs/ImageBuilder/ImageBuilderDialog.js +3 -14
  5. package/dist/Dialogs/ImageBuilder/imageBuilderUtils.js +47 -5
  6. package/dist/Dialogs/QuickCreateChatbotDialog/QuickCreateChatbotDialog.js +146 -34
  7. package/dist/Dialogs/QuickCreateClassroomDialog/QuickCreateClassroomDialog.js +4 -2
  8. package/dist/Dialogs/QuickCreateCourseDialog/QuickCreateCourseDialog.js +4 -2
  9. package/dist/Dialogs/QuickCreateScenarioDialog/QuickCreateScenarioDialog.js +0 -1
  10. package/dist/Dialogs/QuickCreateTopicDialog/QuickCreateTopicDialog.js +4 -2
  11. package/dist/Editors/Bot/Bot.js +4 -2
  12. package/dist/Editors/Bot/Editor/BotDesigner/AddImageModal/AddImageModal.js +4 -2
  13. package/dist/Editors/Bot/Editor/BotDesigner/BotDesigner.js +4 -2
  14. package/dist/Editors/Bot/Editor/BotDesigner/VarList.js +7 -3
  15. package/dist/Editors/Bot/Editor/Editor.js +6 -3
  16. package/dist/Editors/PhraseList/PhraseList.js +6 -3
  17. package/dist/Editors/Phrases/Phrases.js +4 -2
  18. package/dist/Editors/Roleplay/Roleplay.js +4 -2
  19. package/dist/Exercises/ListenAndSpeak/ListenAndSpeak.js +1 -18
  20. package/dist/Exercises/Meaning/Meaning.js +1 -18
  21. package/dist/Exercises/Translator/Translator.js +27 -7
  22. package/dist/Forms/CreateBot/Steps/BotInformation/BotInformation.js +4 -2
  23. package/dist/Forms/CreateClassroom/Steps/Customization/Customization.js +6 -3
  24. package/dist/Forms/CreateCourse/Steps/Customization/Customization.js +6 -3
  25. package/dist/Forms/CreateMeetingMultiStepForm/CreateMeetingMultiStepForm.js +6 -3
  26. package/dist/Forms/CreateMeetingMultiStepForm/MeetingForm.js +6 -3
  27. package/dist/Forms/CreateRoleplay/Steps/RoleplayInformation/RoleplayInformation.js +4 -2
  28. package/dist/Forms/CreateTopic/Steps/Customization/Customization.js +6 -3
  29. package/dist/Forms/ImageSelector/ImageSelector.js +39 -9
  30. package/dist/Forms/UpdateBot/UpdateBot.js +3 -1
  31. package/dist/Forms/UpdateClassroom/UpdateClassroom.js +3 -1
  32. package/dist/Forms/UpdateCourse/UpdateCourse.js +3 -1
  33. package/dist/Forms/UpdatePhraseList/UpdatePhraseList.js +4 -2
  34. package/dist/Forms/UpdateRoleplay/UpdateRoleplay.js +4 -2
  35. package/dist/Forms/UpdateTopic/UpdateTopic.js +3 -1
  36. package/dist/Forms/VoiceSelector/VoiceSelector.js +713 -296
  37. package/dist/Forms/VoiceSelector/VoiceSelector.test.js +247 -4
  38. package/dist/Lists/CourseOutline/CourseOutline.js +3 -1
  39. package/dist/Lists/Exercises/Exercises.js +8 -3
  40. package/dist/Lists/Phrases/Phrases.js +4 -2
  41. package/dist/Misc/NotFound/ClassCourses.js +2 -1
  42. package/dist/Misc/NotFound/Courses.js +2 -1
  43. package/dist/Navigation/ResponsiveTabs/ResponsiveTabs.js +1 -1
  44. package/dist/Tables/MeetingPrompstList/MeetingPromptsList.js +4 -2
  45. package/dist/hooks/useExerciseState.js +92 -26
  46. package/dist/utils/canvasUtils.js +1 -1
  47. package/dist/utils/voices.js +26 -0
  48. package/package.json +1 -1
  49. package/dist/Forms/CreateRoleplayHook/CreateRoleplayHook.js +0 -83
  50. package/dist/Forms/CreateRoleplayHook/CreateRoleplayHook.test.js +0 -20
  51. package/dist/Forms/CreateRoleplayHook/Steps/Confirmation/Confirmation.js +0 -174
  52. package/dist/Forms/CreateRoleplayHook/Steps/Confirmation/package.json +0 -6
  53. package/dist/Forms/CreateRoleplayHook/Steps/RoleplayInformation/RoleplayInformation.js +0 -487
  54. package/dist/Forms/CreateRoleplayHook/Steps/RoleplayInformation/package.json +0 -6
  55. package/dist/Forms/CreateRoleplayHook/Steps/RoleplaySettings/RoleplaySettings.js +0 -330
  56. package/dist/Forms/CreateRoleplayHook/Steps/RoleplaySettings/package.json +0 -6
  57. package/dist/Forms/CreateRoleplayHook/Steps/index.js +0 -4
  58. package/dist/Forms/CreateRoleplayHook/package.json +0 -6
@@ -30,7 +30,8 @@ const AssignmentCardsList = ({
30
30
  isLoadingAssignments,
31
31
  isChallengeModeStudent,
32
32
  isReadWriteModeStudent,
33
- scrollableTarget = null
33
+ scrollableTarget = null,
34
+ openSnackbar
34
35
  }) => {
35
36
  const [lastClickedExerciseId, setLastClickedExerciseId] = useState(null);
36
37
  const [filter, setFilter] = useState("all");
@@ -355,7 +356,8 @@ const AssignmentCardsList = ({
355
356
  handleViewProgress: handleViewProgress,
356
357
  assignedStudents: assignmentMembersById[assignment.assignmentId] || [],
357
358
  isChallengeModeStudent: isChallengeModeStudent,
358
- isReadWriteModeStudent: isReadWriteModeStudent
359
+ isReadWriteModeStudent: isReadWriteModeStudent,
360
+ openSnackbar: openSnackbar
359
361
  }, assignment.assignmentId))
360
362
  })]
361
363
  });
@@ -57,6 +57,7 @@ function CreatePhrase({
57
57
  verificationStatus,
58
58
  makeChatGptApiRequest,
59
59
  difficulty,
60
+ openSnackbar,
60
61
  ...otherProps
61
62
  }) {
62
63
  const translationInputRef = useRef();
@@ -723,7 +724,8 @@ function CreatePhrase({
723
724
  autoSuggest: true,
724
725
  autoSuggestedImages: autoSuggestedImages,
725
726
  selectedImage: selectedImage,
726
- verificationStatus: verificationStatus
727
+ verificationStatus: verificationStatus,
728
+ openSnackbar: openSnackbar
727
729
  })
728
730
  })]
729
731
  })]
@@ -71,7 +71,8 @@ export default function CreatePhraseGroupDialog({
71
71
  onCreated,
72
72
  t = text => text,
73
73
  fileSizeLimit,
74
- verificationStatus
74
+ verificationStatus,
75
+ openSnackbar
75
76
  }) {
76
77
  const [name, setName] = useState("");
77
78
  const [description, setDescription] = useState("");
@@ -180,7 +181,8 @@ export default function CreatePhraseGroupDialog({
180
181
  autoCrop: true,
181
182
  autoSuggest: false,
182
183
  selectedImage: selectedImage,
183
- verificationStatus: verificationStatus
184
+ verificationStatus: verificationStatus,
185
+ openSnackbar: openSnackbar
184
186
  })
185
187
  })]
186
188
  })
@@ -7,13 +7,8 @@ import ImageBuilderControls from "./ImageBuilderControls";
7
7
  import ImageBuilderPreview from "./ImageBuilderPreview";
8
8
  import ResponsiveDialog from "../ResponsiveDialog/ResponsiveDialog";
9
9
  import { PRESET_COLORS, ICON_KEYS } from "./iconPaths";
10
- import { generatePatternCanvas, getCenterCropDataUrl, getOptimalIconOpacity, shouldUseDarkIcons } from "./imageBuilderUtils";
11
-
12
- // Matches placeholder SVG ratio (21144 x 4890), scaled down 10x
10
+ import { generateBuilderImages, getOptimalIconOpacity, shouldUseDarkIcons } from "./imageBuilderUtils";
13
11
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
- const BANNER_W = 2114;
15
- const BANNER_H = 489;
16
- const CARD_SIZE = 489;
17
12
  const useStyles = makeStyles()(theme => ({
18
13
  body: {
19
14
  display: "flex",
@@ -51,19 +46,13 @@ export default function ImageBuilderDialog({
51
46
  cardDataUrl: null
52
47
  };
53
48
  }
54
- const canvas = generatePatternCanvas({
49
+ return generateBuilderImages({
55
50
  color,
56
51
  iconKey,
57
52
  density: "dense",
58
53
  iconOpacity,
59
- darkIcons,
60
- width: BANNER_W,
61
- height: BANNER_H
54
+ darkIcons
62
55
  });
63
- return {
64
- bannerDataUrl: canvas.toDataURL("image/png"),
65
- cardDataUrl: getCenterCropDataUrl(canvas, CARD_SIZE)
66
- };
67
56
  }, [color, iconKey, iconOpacity, darkIcons]);
68
57
 
69
58
  // Reset state when dialog opens
@@ -4,6 +4,19 @@ const DENSITY_COLS = {
4
4
  medium: 10,
5
5
  dense: 14
6
6
  };
7
+
8
+ // Base (logical) dimensions of a builder image. Matches placeholder SVG ratio
9
+ // (21144 x 4890), scaled down 10x. The card is a centre-cropped square.
10
+ export const BANNER_W = 2114;
11
+ export const BANNER_H = 489;
12
+ export const CARD_SIZE = 489;
13
+
14
+ // Supersampling factor applied to the generated pixel dimensions so the stored
15
+ // image stays crisp when scaled up on large / HiDPI screens. Device-independent
16
+ // on purpose — the image is persisted and shared across devices, so we must not
17
+ // bake window.devicePixelRatio into it. Tune this single knob to trade payload
18
+ // size against sharpness.
19
+ export const OUTPUT_SCALE = 2;
7
20
  function hexToRgb(hex) {
8
21
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
9
22
  return result ? {
@@ -87,6 +100,7 @@ export function generatePatternCanvas({
87
100
  canvas.width = width;
88
101
  canvas.height = height;
89
102
  const ctx = canvas.getContext("2d");
103
+ ctx.imageSmoothingQuality = "high";
90
104
 
91
105
  // 1. Solid background
92
106
  ctx.fillStyle = color;
@@ -107,10 +121,18 @@ export function generatePatternCanvas({
107
121
  };
108
122
  const maxDim = Math.max(vb.width, vb.height);
109
123
  const scale = iconSize / maxDim;
124
+
125
+ // Rasterise the stamp at its final on-canvas size by filling the vector paths
126
+ // through a scaled context. Previously the stamp was drawn at the raw viewBox
127
+ // size (e.g. 24px) and then upscaled ~4x when blitted onto the pattern canvas,
128
+ // which baked a blurry raster upscale into the source image. Filling from
129
+ // vectors at the target size keeps icons crisp while preserving the single
130
+ // stamp (so overlapping sub-paths don't double-composite at globalAlpha < 1).
110
131
  const stampCanvas = document.createElement("canvas");
111
- stampCanvas.width = vb.width;
112
- stampCanvas.height = vb.height;
132
+ stampCanvas.width = Math.ceil(vb.width * scale);
133
+ stampCanvas.height = Math.ceil(vb.height * scale);
113
134
  const stampCtx = stampCanvas.getContext("2d");
135
+ stampCtx.scale(scale, scale);
114
136
  stampCtx.fillStyle = darkIcons ? "#000" : "#fff";
115
137
  const path2Ds = iconData.paths.map(d => new Path2D(d));
116
138
  for (const p of path2Ds) {
@@ -131,9 +153,8 @@ export function generatePatternCanvas({
131
153
  ctx.save();
132
154
  ctx.translate(x, y);
133
155
  ctx.rotate(rotation);
134
- ctx.scale(scale, scale);
135
- ctx.translate(-vb.width / 2, -vb.height / 2);
136
- ctx.drawImage(stampCanvas, 0, 0);
156
+ // Draw the pre-scaled stamp 1:1 (centred) — no upscaling here.
157
+ ctx.drawImage(stampCanvas, -stampCanvas.width / 2, -stampCanvas.height / 2);
137
158
  ctx.restore();
138
159
  }
139
160
  }
@@ -156,4 +177,25 @@ export function getCenterCropDataUrl(source, size) {
156
177
  const sx = (source.width - source.height) / 2;
157
178
  ctx.drawImage(source, sx, 0, source.height, source.height, 0, 0, size, size);
158
179
  return cropCanvas.toDataURL("image/png");
180
+ }
181
+
182
+ /**
183
+ * Generate the builder images (banner + centre-cropped card) at the shared
184
+ * supersampled output resolution. Single source of truth for the builder
185
+ * dimensions so callers don't duplicate the magic numbers.
186
+ *
187
+ * @param {Object} opts – forwarded to generatePatternCanvas (color, iconKey,
188
+ * density, iconOpacity, darkIcons, seed)
189
+ * @returns {{ bannerDataUrl: string, cardDataUrl: string }}
190
+ */
191
+ export function generateBuilderImages(opts) {
192
+ const canvas = generatePatternCanvas({
193
+ ...opts,
194
+ width: BANNER_W * OUTPUT_SCALE,
195
+ height: BANNER_H * OUTPUT_SCALE
196
+ });
197
+ return {
198
+ bannerDataUrl: canvas.toDataURL("image/png"),
199
+ cardDataUrl: getCenterCropDataUrl(canvas, CARD_SIZE * OUTPUT_SCALE)
200
+ };
159
201
  }
@@ -1,10 +1,14 @@
1
- import { useState, useEffect, forwardRef, useMemo, createElement } from "react";
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
- // exclude legacy for random selection
427
- const nonLegacyVoicePool = voicesArray.filter(v => !v.voice?.legacy && !v.legacy);
428
- const voicePool = nonLegacyVoicePool.length > 0 ? nonLegacyVoicePool : voicesArray;
429
- const randomIndex = Math.floor(Math.random() * voicePool.length);
430
- const randomVoiceObj = voicePool[randomIndex] || {};
431
- const {
432
- voice: randomVoice,
433
- dialect
434
- } = randomVoiceObj;
435
- const hasAvailableVoice = Boolean(randomVoice?.name && dialect?.languageCode);
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: randomVoice.name,
453
- voiceLanguageCode: dialect.languageCode,
454
- name: randomVoice.name,
455
- botAvatar: randomVoice.userImage,
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__*/_jsx(ResponsiveDialog, {
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
  }
@@ -80,7 +80,8 @@ export default function QuickCreateClassroomDialog({
80
80
  userImage,
81
81
  forLang,
82
82
  languages,
83
- forLanguages
83
+ forLanguages,
84
+ openSnackbar
84
85
  }) {
85
86
  const [classroomName, setClassroomName] = useState("");
86
87
  const [classroomDescription, setClassroomDescription] = useState("");
@@ -275,7 +276,8 @@ export default function QuickCreateClassroomDialog({
275
276
  autoSuggest: false,
276
277
  selectedImage: selectedImage,
277
278
  verificationStatus: verificationStatus,
278
- onBuilderSettingsChange: setBuilderSettings
279
+ onBuilderSettingsChange: setBuilderSettings,
280
+ openSnackbar: openSnackbar
279
281
  })
280
282
  })]
281
283
  })
@@ -83,7 +83,8 @@ export default function QuickCreateCourseDialog({
83
83
  difficulties = ["A1", "A2", "B1", "B2", "C1", "C2"],
84
84
  fileSizeLimit,
85
85
  verificationStatus,
86
- userImage
86
+ userImage,
87
+ openSnackbar
87
88
  }) {
88
89
  const defaultSelectedImage = {
89
90
  image: "",
@@ -289,7 +290,8 @@ export default function QuickCreateCourseDialog({
289
290
  autoSuggest: false,
290
291
  selectedImage: selectedImage,
291
292
  verificationStatus: verificationStatus,
292
- onBuilderSettingsChange: setBuilderSettings
293
+ onBuilderSettingsChange: setBuilderSettings,
294
+ openSnackbar: openSnackbar
293
295
  })
294
296
  })]
295
297
  })
@@ -330,7 +330,6 @@ export default function QuickCreateScenarioDialog({
330
330
  isUserInternal: isUserInternal,
331
331
  handleSpeak: handleSpeak,
332
332
  avatars: avatars,
333
- hidePitch: true,
334
333
  disabled: isLoading
335
334
  })
336
335
  }), /*#__PURE__*/_jsx(Grid, {
@@ -83,7 +83,8 @@ export default function QuickCreateTopicDialog({
83
83
  userImage,
84
84
  coursePicture,
85
85
  coursePictureBuilderColor,
86
- coursePictureBuilderIcon
86
+ coursePictureBuilderIcon,
87
+ openSnackbar
87
88
  }) {
88
89
  // Only inherit the parent course image when it's a real user-set image.
89
90
  // If the course has no image, or its image was itself generated by the image
@@ -274,7 +275,8 @@ export default function QuickCreateTopicDialog({
274
275
  autoSuggest: false,
275
276
  selectedImage: selectedImage,
276
277
  verificationStatus: verificationStatus,
277
- onBuilderSettingsChange: setBuilderSettings
278
+ onBuilderSettingsChange: setBuilderSettings,
279
+ openSnackbar: openSnackbar
278
280
  })
279
281
  })]
280
282
  })
@@ -770,7 +770,8 @@ function Bot({
770
770
  courseId: courseId,
771
771
  sectionId: sectionId,
772
772
  topicId: topicId,
773
- initialTopicGoal: initialTopicGoal
773
+ initialTopicGoal: initialTopicGoal,
774
+ openSnackbar: openSnackbar
774
775
  }) : null, tabValue === 1 ? /*#__PURE__*/_jsx(Box, {
775
776
  className: hidden ? classes.paneFull : classes.pane,
776
777
  children: BotPreview
@@ -791,7 +792,8 @@ function Bot({
791
792
  subscription: subscription,
792
793
  handleBotInstructionsImageUpload: handleBotInstructionsImageUpload,
793
794
  isUserInternal: isUserInternal,
794
- verificationStatus: verificationStatus
795
+ verificationStatus: verificationStatus,
796
+ openSnackbar: openSnackbar
795
797
  })
796
798
  }) : null]
797
799
  }), /*#__PURE__*/_jsx(ResponsiveDialog, {
@@ -14,7 +14,8 @@ function AddImageModal(props) {
14
14
  open,
15
15
  handleUploadBotExerciseImage,
16
16
  botId,
17
- verificationStatus
17
+ verificationStatus,
18
+ openSnackbar
18
19
  } = props;
19
20
  const theme = useTheme();
20
21
  const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
@@ -76,7 +77,8 @@ function AddImageModal(props) {
76
77
  name: "image",
77
78
  value: selectedImage.image,
78
79
  handleChange: handleImageChange,
79
- verificationStatus: verificationStatus
80
+ verificationStatus: verificationStatus,
81
+ openSnackbar: openSnackbar
80
82
  })]
81
83
  }), /*#__PURE__*/_jsxs(DialogActions, {
82
84
  children: [/*#__PURE__*/_jsx(Button, {
@@ -36,7 +36,8 @@ function BotDesigner(props) {
36
36
  handleUpdateAllVariables,
37
37
  handleUploadBotExerciseImage,
38
38
  botId,
39
- handleOnBlur
39
+ handleOnBlur,
40
+ openSnackbar
40
41
  } = props;
41
42
  return /*#__PURE__*/_jsxs(_Fragment, {
42
43
  children: [rivescriptJSON && rivescriptJSON.begin.var && /*#__PURE__*/_jsx(VarList, {
@@ -49,7 +50,8 @@ function BotDesigner(props) {
49
50
  handleUploadBotExerciseImage: handleUploadBotExerciseImage,
50
51
  botId: botId,
51
52
  handleOnBlur: handleOnBlur,
52
- handleUpdateVariable: handleUpdateVariable
53
+ handleUpdateVariable: handleUpdateVariable,
54
+ openSnackbar: openSnackbar
53
55
  }), rivescriptJSON && rivescriptJSON.begin.array && /*#__PURE__*/_jsx(AlternativesList, {
54
56
  t: t,
55
57
  alternatives: rivescriptJSON.begin.array,
@@ -26,7 +26,8 @@ function VarListItem({
26
26
  handleOnBlur,
27
27
  handleUploadBotExerciseImage,
28
28
  verificationStatus,
29
- botId
29
+ botId,
30
+ openSnackbar
30
31
  }) {
31
32
  const [selectedImage, setSelectedImage] = useState("");
32
33
  if (varValue && markdownImageLinkRegex.test(varValue)) {
@@ -72,6 +73,7 @@ function VarListItem({
72
73
  handleChange: handleImageChange,
73
74
  cropCompletedCallback: cropCompletedCallback,
74
75
  verificationStatus: verificationStatus,
76
+ openSnackbar: openSnackbar,
75
77
  autoGenerateDefault: false
76
78
  // fileSizeLimit={fileSizeLimit}
77
79
  ,
@@ -256,7 +258,8 @@ function VarList({
256
258
  handleSelectCurrentInput,
257
259
  handleUploadBotExerciseImage,
258
260
  botId,
259
- handleOnBlur
261
+ handleOnBlur,
262
+ openSnackbar
260
263
  }) {
261
264
  const [open, setOpen] = useState(true);
262
265
  const [varUploader, setVarUploader] = useState(false);
@@ -276,7 +279,8 @@ function VarList({
276
279
  handleUploadBotExerciseImage: handleUploadBotExerciseImage,
277
280
  handleOnBlur: handleOnBlur,
278
281
  botId: botId,
279
- handleUpdateVariable: handleUpdateVariable
282
+ handleUpdateVariable: handleUpdateVariable,
283
+ openSnackbar: openSnackbar
280
284
  }));
281
285
  }
282
286
  return /*#__PURE__*/_jsxs(List, {
@@ -93,7 +93,8 @@ function Editor({
93
93
  sectionId,
94
94
  topicId,
95
95
  initialTopicGoal,
96
- verificationStatus
96
+ verificationStatus,
97
+ openSnackbar
97
98
  }) {
98
99
  const [formattingOptionsAnchorEl, setFormattingOptionsAnchorEl] = useState(null);
99
100
  const isFormattingOptionsOpen = Boolean(formattingOptionsAnchorEl);
@@ -566,7 +567,8 @@ function Editor({
566
567
  handleUpdateAlternatives: handleUpdateAlternatives,
567
568
  handleUploadBotExerciseImage: handleUploadBotExerciseImage,
568
569
  botId: bot.botId,
569
- handleUpdateVariable: handleUpdateVariable
570
+ handleUpdateVariable: handleUpdateVariable,
571
+ openSnackbar: openSnackbar
570
572
  }), /*#__PURE__*/_jsx(TriggerDialog, {
571
573
  t: t,
572
574
  open: isTriggerModalOpen,
@@ -602,7 +604,8 @@ function Editor({
602
604
  botId: bot.botId,
603
605
  handleUploadBotExerciseImage: handleUploadBotExerciseImage,
604
606
  handleAddImage: handleAddImage,
605
- verificationStatus: verificationStatus
607
+ verificationStatus: verificationStatus,
608
+ openSnackbar: openSnackbar
606
609
  }), /*#__PURE__*/_jsx(AddLinkModal, {
607
610
  classes: {},
608
611
  t: t,
@@ -96,7 +96,8 @@ function PhraseList({
96
96
  otherPhraseLists,
97
97
  otherPhraseListsLoading,
98
98
  handleTransferPhrase,
99
- handleCreatePhraseListFromSelection
99
+ handleCreatePhraseListFromSelection,
100
+ openSnackbar
100
101
  }) {
101
102
  const theme = useTheme();
102
103
  const {
@@ -357,7 +358,8 @@ function PhraseList({
357
358
  isGeneratePhrasesDialogOpen: isGeneratePhrasesDialogOpen,
358
359
  setIsGeneratePhrasesDialogOpen: setIsGeneratePhrasesDialogOpen,
359
360
  mode: mode,
360
- scriptDownloadRef: scriptDownloadRef
361
+ scriptDownloadRef: scriptDownloadRef,
362
+ openSnackbar: openSnackbar
361
363
  })
362
364
  }) : null, tabValue === 1 ? /*#__PURE__*/_jsx(Box, {
363
365
  sx: {
@@ -378,7 +380,8 @@ function PhraseList({
378
380
  onSubmitExerciseSettings: onSubmitExerciseSettings,
379
381
  handleSpeak: handleSpeak,
380
382
  fileSizeLimit: fileSizeLimit,
381
- verificationStatus: verificationStatus
383
+ verificationStatus: verificationStatus,
384
+ openSnackbar: openSnackbar
382
385
  })
383
386
  }) : null]
384
387
  })