@nualang/nualang-ui-components 0.1.1425 → 0.1.1427
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.
|
@@ -488,7 +488,7 @@ function RoleplayGame({
|
|
|
488
488
|
const phraseWordList = nextScriptLine ? removeExtraWhiteSpaces(removeAllSymbols(nextScriptLine.text)).split(" ") : [];
|
|
489
489
|
useEffect(() => {
|
|
490
490
|
// Check if recording has ended and has detected at least one word
|
|
491
|
-
if (open && !recognizing && finalTranscript) {
|
|
491
|
+
if (open && !recognizing && finalTranscript && nextScriptLine?.text) {
|
|
492
492
|
// Submit attempt
|
|
493
493
|
checkRoleplayPronunciation(finalTranscript, nextScriptLine.text);
|
|
494
494
|
}
|
|
@@ -531,7 +531,7 @@ function RoleplayGame({
|
|
|
531
531
|
},
|
|
532
532
|
children: /*#__PURE__*/_jsx(AnswerOptions, {
|
|
533
533
|
t: t,
|
|
534
|
-
text: nextScriptLine
|
|
534
|
+
text: nextScriptLine?.text,
|
|
535
535
|
options: answerOptions,
|
|
536
536
|
handleSelectAnswerOption: handleSelectAnswerOption,
|
|
537
537
|
isActorOne: isActorOne,
|
|
@@ -480,7 +480,7 @@ function RoleplayGame({
|
|
|
480
480
|
const phraseWordList = nextScriptLine ? removeExtraWhiteSpaces(removeAllSymbols(nextScriptLine.text)).split(" ") : [];
|
|
481
481
|
useEffect(() => {
|
|
482
482
|
// Check if recording has ended and has detected at least one word
|
|
483
|
-
if (open && !recognizing && finalTranscript) {
|
|
483
|
+
if (open && !recognizing && finalTranscript && nextScriptLine?.text) {
|
|
484
484
|
// Submit attempt
|
|
485
485
|
checkRoleplayPronunciation(finalTranscript, nextScriptLine.text);
|
|
486
486
|
}
|
|
@@ -524,7 +524,7 @@ function RoleplayGame({
|
|
|
524
524
|
children: /*#__PURE__*/_jsx(AnswerOptions, {
|
|
525
525
|
t: t,
|
|
526
526
|
ariaLiveText: ariaLiveText,
|
|
527
|
-
text: nextScriptLine
|
|
527
|
+
text: nextScriptLine?.text,
|
|
528
528
|
options: answerOptions,
|
|
529
529
|
handleSelectAnswerOption: handleSelectAnswerOption,
|
|
530
530
|
isActorOne: isActorOne,
|
|
@@ -515,7 +515,7 @@ export default function useExerciseState({
|
|
|
515
515
|
setTimeout(() => {
|
|
516
516
|
handleOpenConfidence();
|
|
517
517
|
}, 500);
|
|
518
|
-
if (checkIsRoleplayMessageCorrect(finalTranscript, currentScriptLine.text)) {
|
|
518
|
+
if (currentScriptLine && checkIsRoleplayMessageCorrect(finalTranscript, currentScriptLine.text)) {
|
|
519
519
|
checkRoleplayPronunciation(finalTranscript, currentScriptLine.text);
|
|
520
520
|
}
|
|
521
521
|
}
|
|
@@ -525,7 +525,7 @@ export default function useExerciseState({
|
|
|
525
525
|
const script = roleplay.script;
|
|
526
526
|
const currentScriptLine = script[roleplayMessages.length];
|
|
527
527
|
// Check if final + interim transcript is the correct answer,
|
|
528
|
-
if (checkIsRoleplayMessageCorrect(`${finalTranscript || ""} ${interimTranscript || ""}`, currentScriptLine.text)) {
|
|
528
|
+
if (currentScriptLine && checkIsRoleplayMessageCorrect(`${finalTranscript || ""} ${interimTranscript || ""}`, currentScriptLine.text)) {
|
|
529
529
|
// End recogniton if the answer is correct (Indicate end stream to get final results)
|
|
530
530
|
indicateEndStream();
|
|
531
531
|
}
|
|
@@ -788,6 +788,15 @@ export default function useExerciseState({
|
|
|
788
788
|
const handleNext = updatedAnswerAttempts => {
|
|
789
789
|
const script = roleplay.script;
|
|
790
790
|
const currentScriptLine = script[roleplayMessages.length];
|
|
791
|
+
// Guard against an out-of-range index: if the messages have caught up with
|
|
792
|
+
// (or overrun) the script there is no next line to advance to, so end the
|
|
793
|
+
// roleplay rather than dereferencing an undefined script line.
|
|
794
|
+
if (!currentScriptLine) {
|
|
795
|
+
if (roleplayHasStarted) {
|
|
796
|
+
roleplayHasEnded(0, updatedAnswerAttempts);
|
|
797
|
+
}
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
791
800
|
if (gameId === "story") {
|
|
792
801
|
if (currentScriptLine) {
|
|
793
802
|
if (currentScriptLine?.question) {
|
|
@@ -952,6 +961,9 @@ export default function useExerciseState({
|
|
|
952
961
|
setDisableSkip(true);
|
|
953
962
|
const script = roleplay.script;
|
|
954
963
|
const currentScriptLine = script[roleplayMessages.length];
|
|
964
|
+
if (!currentScriptLine) {
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
955
967
|
setAnswerAttempts(prevAttempts => [...prevAttempts, {
|
|
956
968
|
type: "script",
|
|
957
969
|
attempt: roleplayText,
|
|
@@ -1027,7 +1039,11 @@ export default function useExerciseState({
|
|
|
1027
1039
|
};
|
|
1028
1040
|
const deleteProgress = () => {
|
|
1029
1041
|
if (courseId && sectionId && topicId) {
|
|
1030
|
-
handleDeleteProgress(courseId, sectionId, topicId, `${exerciseName}${gameId && gameId !== "original" ? `-${gameId}` : ""}
|
|
1042
|
+
handleDeleteProgress(courseId, sectionId, topicId, `${exerciseName}${gameId && gameId !== "original" ? `-${gameId}` : ""}`, {
|
|
1043
|
+
roleplayId: roleplay?.roleplayId,
|
|
1044
|
+
botId: bot?.botId,
|
|
1045
|
+
phraseListId
|
|
1046
|
+
});
|
|
1031
1047
|
} else if (exerciseName === "roleplay") {
|
|
1032
1048
|
handleDeleteRoleplayProgress(roleplay.roleplayId, `${exerciseName}${gameId && gameId !== "original" ? `-${gameId}` : ""}`);
|
|
1033
1049
|
} else if (exerciseName === "bot") {
|
|
@@ -1509,7 +1525,9 @@ export default function useExerciseState({
|
|
|
1509
1525
|
setAnswerAttempts(newAnswerAttempts);
|
|
1510
1526
|
const script = roleplay.script;
|
|
1511
1527
|
const currentScriptLine = script[roleplayMessages.length];
|
|
1512
|
-
|
|
1528
|
+
if (currentScriptLine) {
|
|
1529
|
+
addRoleplayMessage(currentScriptLine.text, currentScriptLine.actor === 2, true);
|
|
1530
|
+
}
|
|
1513
1531
|
setRoleplayText("");
|
|
1514
1532
|
setDisableSkip(true);
|
|
1515
1533
|
}
|
|
@@ -348,11 +348,19 @@ export default function useRecognition(props = {}) {
|
|
|
348
348
|
return;
|
|
349
349
|
}
|
|
350
350
|
audioInputRef.current.connect(processorRef.current);
|
|
351
|
+
|
|
352
|
+
// Resolved on the worklet's first real audio frame - the signal that
|
|
353
|
+
// capture has actually started, not just that permission/resume
|
|
354
|
+
// resolved. Capped so a genuinely silent/broken input can't hang
|
|
355
|
+
// start() forever; the cap is a safety net, not the primary signal.
|
|
356
|
+
let resolveFirstAudioFrame;
|
|
357
|
+
const firstAudioFrame = new Promise(resolve => {
|
|
358
|
+
resolveFirstAudioFrame = resolve;
|
|
359
|
+
});
|
|
360
|
+
const firstAudioFrameTimeoutId = setTimeout(resolveFirstAudioFrame, 800);
|
|
351
361
|
processorRef.current.port.onmessage = event => {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
};
|
|
355
|
-
processorRef.current.port.onmessage = event => {
|
|
362
|
+
clearTimeout(firstAudioFrameTimeoutId);
|
|
363
|
+
resolveFirstAudioFrame();
|
|
356
364
|
const audioData = event.data;
|
|
357
365
|
let sampleRate = 44100; // Default sample rate
|
|
358
366
|
if (audioContextRef?.current?.sampleRate && audioContextRef?.current?.sampleRate !== 44100) {
|
|
@@ -387,14 +395,12 @@ export default function useRecognition(props = {}) {
|
|
|
387
395
|
mediaRecorderRef.current = null;
|
|
388
396
|
}
|
|
389
397
|
}
|
|
390
|
-
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return new Promise(resolve => setTimeout(resolve, 500));
|
|
397
|
-
}
|
|
398
|
+
|
|
399
|
+
// recognizing:true (which flips the mic button to its "recording"
|
|
400
|
+
// state) must not fire until this resolves - otherwise Safari can
|
|
401
|
+
// show the button as live before AVAudioSession route negotiation
|
|
402
|
+
// has actually let samples through, losing the start of speech.
|
|
403
|
+
await firstAudioFrame;
|
|
398
404
|
};
|
|
399
405
|
if (navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
400
406
|
// Re-assert in case TTS playback downgraded the session since mount.
|