@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.2
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/api/exam-create.js +1 -179
- package/dist/api/exam-create.types.js +1 -1
- package/dist/api/exam-questions.js +1 -98
- package/dist/api/exam-questions.types.js +1 -1
- package/dist/api/exam-taking.js +1 -73
- package/dist/api/exam-taking.types.js +1 -1
- package/dist/api/index.js +1 -6
- package/dist/api/result-review.js +1 -50
- package/dist/api/result-review.types.js +1 -1
- package/dist/components/CueCardEditor.js +1 -58
- package/dist/components/common/ResolvedImage.js +1 -15
- package/dist/components/exams/CreateExamInfoForm.js +1 -35
- package/dist/components/exams/CreateExamPageContainer.js +1 -103
- package/dist/components/exams/ExamCreator.js +1 -89
- package/dist/components/exams/ExamPartTabs.js +1 -40
- package/dist/components/exams/ExamQuestionGrid.js +1 -18
- package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
- package/dist/components/exams/PartAudioUploader.js +1 -9
- package/dist/components/exams/TemplateSelector.js +1 -66
- package/dist/components/exams/api.js +1 -3
- package/dist/components/exams/create-exam.validation.js +1 -18
- package/dist/components/exams/exam-template.utils.js +1 -18
- package/dist/components/exams/index.js +1 -10
- package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
- package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
- package/dist/components/exams/take/exam-taking.utils.js +1 -88
- package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
- package/dist/components/exams/take/index.js +1 -2
- package/dist/components/exams/take/types.js +1 -1
- package/dist/components/index.js +1 -42
- package/dist/components/questions/_shared/config/question-types.config.js +1 -333
- package/dist/components/questions/_shared/constants/color-palette.js +1 -21
- package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
- package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
- package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
- package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
- package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
- package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
- package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
- package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
- package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
- package/dist/components/questions/_shared/index.js +1 -7
- package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
- package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
- package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
- package/dist/components/questions/_shared/types/color-region.config.js +1 -1
- package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
- package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
- package/dist/components/questions/_shared/types/index.js +1 -12
- package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
- package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
- package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
- package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
- package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
- package/dist/components/questions/_shared/types/question-group.type.js +1 -11
- package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
- package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
- package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
- package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
- package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
- package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
- package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
- package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
- package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
- package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
- package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
- package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
- package/dist/components/questions/_shared/utils/id-generator.js +1 -3
- package/dist/components/questions/_shared/utils/question-validation.js +1 -21
- package/dist/components/questions/components/ColorNode.js +1 -85
- package/dist/components/questions/components/ColorRegionClient.js +1 -545
- package/dist/components/questions/components/ColorRegionCreator.js +1 -420
- package/dist/components/questions/components/ColorRegionNode.js +1 -59
- package/dist/components/questions/components/CreatorGuide.js +1 -103
- package/dist/components/questions/components/NameNode.js +1 -26
- package/dist/components/questions/components/QuestionBankOption.js +1 -46
- package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
- package/dist/components/questions/components/QuestionCommonFields.js +1 -33
- package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
- package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
- package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
- package/dist/components/questions/components/RegionNode.js +1 -94
- package/dist/components/questions/components/index.js +1 -14
- package/dist/components/questions/creator/QuestionCreator.js +1 -90
- package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
- package/dist/components/questions/creator/dedicated-component-router.js +1 -79
- package/dist/components/questions/creator/index.js +1 -3
- package/dist/components/questions/creator/question-type-registry.js +1 -80
- package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
- package/dist/components/questions/index.js +1 -10
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
- package/dist/components/questions/types/answer-the-question/register.js +1 -67
- package/dist/components/questions/types/answer-the-question/transform.js +1 -36
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
- package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
- package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
- package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
- package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
- package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
- package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
- package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
- package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
- package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
- package/dist/components/questions/types/fill-in-blank/index.js +1 -2
- package/dist/components/questions/types/fill-in-blank/register.js +1 -138
- package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
- package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
- package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
- package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
- package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
- package/dist/components/questions/types/image-object-matching/index.js +1 -2
- package/dist/components/questions/types/image-object-matching/register.js +1 -3
- package/dist/components/questions/types/image-object-matching/transform.js +1 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
- package/dist/components/questions/types/label-the-picture/index.js +1 -2
- package/dist/components/questions/types/label-the-picture/register.js +1 -54
- package/dist/components/questions/types/label-the-picture/transform.js +1 -28
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
- package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
- package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
- package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
- package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
- package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
- package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
- package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
- package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
- package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
- package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
- package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
- package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
- package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
- package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
- package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
- package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
- package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
- package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
- package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
- package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
- package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
- package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
- package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
- package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
- package/dist/components/questions/types/read-displayed-content/register.js +1 -64
- package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
- package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
- package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
- package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
- package/dist/components/questions/types/speaking-conversation/register.js +1 -110
- package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
- package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
- package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
- package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
- package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
- package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
- package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
- package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
- package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
- package/dist/components/questions/types/true-false/index.js +1 -2
- package/dist/components/questions/types/true-false/register.js +1 -77
- package/dist/components/questions/types/true-false/transform.js +1 -32
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
- package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
- package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
- package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
- package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
- package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
- package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
- package/dist/components/questions/types/word-ordering/index.js +1 -2
- package/dist/components/questions/types/word-ordering/register.js +1 -46
- package/dist/components/questions/types/word-ordering/transform.js +1 -24
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
- package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
- package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
- package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
- package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
- package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
- package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
- package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
- package/dist/components/questions/types/write-sentences/index.js +1 -2
- package/dist/components/questions/types/write-sentences/register.js +1 -58
- package/dist/components/questions/types/write-sentences/transform.js +1 -28
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
- package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
- package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
- package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
- package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
- package/dist/components/questions/viewer/QuestionViewer.js +1 -446
- package/dist/components/questions/viewer/index.js +1 -2
- package/dist/components/results/ResultReviewPageContainer.js +1 -98
- package/dist/components/results/ReviewQuestionRenderer.js +1 -72
- package/dist/components/results/index.js +1 -2
- package/dist/components/results/result-review.utils.js +1 -6
- package/dist/components/results/review-data.js +1 -150
- package/dist/components/results/review-renderer.utils.js +1 -97
- package/dist/components/results/review-types.js +1 -1
- package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
- package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
- package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
- package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
- package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
- package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
- package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
- package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
- package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
- package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
- package/dist/components/themes/cambridge-yle/index.js +1 -13
- package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
- package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
- package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
- package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
- package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
- package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
- package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
- package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
- package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
- package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
- package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
- package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
- package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
- package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
- package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
- package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
- package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
- package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
- package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
- package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
- package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
- package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
- package/dist/components/themes/english-certification/index.js +1 -21
- package/dist/components/themes/index.js +1 -6
- package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
- package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
- package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
- package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
- package/dist/components/themes/types.js +1 -1
- package/dist/components/ui/alert-dialog.js +1 -26
- package/dist/components/ui/alert.js +1 -22
- package/dist/components/ui/avatar.js +1 -12
- package/dist/components/ui/badge.js +1 -38
- package/dist/components/ui/button.js +1 -35
- package/dist/components/ui/calendar.js +1 -76
- package/dist/components/ui/card.js +1 -16
- package/dist/components/ui/carousel.js +1 -91
- package/dist/components/ui/checkbox.js +1 -9
- package/dist/components/ui/collapsible.js +1 -13
- package/dist/components/ui/command.js +1 -34
- package/dist/components/ui/confirm-dialog.js +1 -49
- package/dist/components/ui/dialog.js +1 -23
- package/dist/components/ui/drawer.js +1 -35
- package/dist/components/ui/dropdown-menu.js +1 -33
- package/dist/components/ui/file-upload.js +1 -155
- package/dist/components/ui/form.js +1 -62
- package/dist/components/ui/hover-card.js +1 -10
- package/dist/components/ui/image-preview.js +1 -29
- package/dist/components/ui/input.js +1 -8
- package/dist/components/ui/label.js +1 -9
- package/dist/components/ui/language-switcher.js +1 -15
- package/dist/components/ui/multi-select.js +1 -90
- package/dist/components/ui/points-input.js +1 -33
- package/dist/components/ui/popover.js +1 -26
- package/dist/components/ui/progress.js +1 -16
- package/dist/components/ui/radio-group.js +1 -12
- package/dist/components/ui/scroll-area.js +1 -11
- package/dist/components/ui/select.js +1 -38
- package/dist/components/ui/separator.js +1 -8
- package/dist/components/ui/sheet.js +1 -40
- package/dist/components/ui/single-select.js +1 -76
- package/dist/components/ui/skeleton.js +1 -6
- package/dist/components/ui/switch.js +1 -8
- package/dist/components/ui/table.js +1 -20
- package/dist/components/ui/tabs.js +1 -17
- package/dist/components/ui/textarea.js +1 -8
- package/dist/components/ui/tooltip.js +1 -11
- package/dist/index.js +1 -19
- package/dist/mcp/config.js +1 -32
- package/dist/mcp/http-client.js +1 -48
- package/dist/mcp/index.js +1 -29
- package/dist/mcp/tools/_helpers.js +1 -25
- package/dist/mcp/tools/exams.js +1 -439
- package/dist/mcp/tools/index.js +1 -6
- package/dist/mcp/tools/results.js +1 -19
- package/dist/shared/constants/ApiConstant.js +1 -276
- package/dist/shared/constants/QueryKeyConstant.js +1 -112
- package/dist/shared/constants/exam-level.enum.js +1 -41
- package/dist/shared/constants/landings/placement-test.constants.js +1 -431
- package/dist/shared/constants/question-skills.js +1 -267
- package/dist/shared/constants/routes.js +1 -81
- package/dist/shared/index.js +1 -7
- package/dist/shared/lib/hooks/index.js +1 -10
- package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
- package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
- package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
- package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
- package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
- package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
- package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
- package/dist/shared/lib/hooks/useFileUpload.js +1 -24
- package/dist/shared/lib/hooks/useImageUrl.js +1 -5
- package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
- package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
- package/dist/shared/lib/hooks/useToast.js +1 -62
- package/dist/shared/lib/i18n/index.js +1 -54
- package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/en/common.js +1 -140
- package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/en/student.js +1 -133
- package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/en.js +1 -15
- package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
- package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
- package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
- package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
- package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
- package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
- package/dist/shared/lib/i18n/messages/vi.js +1 -15
- package/dist/shared/lib/i18n/types.js +1 -1
- package/dist/shared/lib/stores/examQuestionStore.js +1 -212
- package/dist/shared/lib/stores/localeStore.js +1 -10
- package/dist/shared/lib/utils/public-page.js +1 -44
- package/dist/shared/lib/utils/question-transform-types.js +1 -1
- package/dist/shared/lib/utils/question-transform.js +1 -174
- package/dist/shared/lib/utils.js +1 -168
- package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
- package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
- package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
- package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
- package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
- package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
- package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
- package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
- package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
- package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
- package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
- package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
- package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
- package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
- package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
- package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
- package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
- package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
- package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
- package/dist/shared/types/branch.types.js +1 -6
- package/dist/shared/types/common.types.js +1 -50
- package/dist/shared/types/entities/exam-schedule.types.js +1 -5
- package/dist/shared/types/entities/exam.types.js +1 -1
- package/dist/shared/types/entities/question.types.js +1 -4
- package/dist/shared/types/entities/result.types.js +1 -4
- package/dist/shared/types/entities/user.types.js +1 -4
- package/dist/shared/types/exam-taking.types.js +1 -2
- package/dist/shared/types/history.types.js +1 -34
- package/dist/shared/types/index.js +1 -1
- package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
- package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
- package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
- package/dist/shared/types/questions/index.js +1 -40
- package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
- package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
- package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
- package/dist/shared/types/questions/read-displayed-content.js +1 -1
- package/dist/shared/types/questions/speaking-conversation.js +1 -7
- package/dist/shared/types/questions/speaking-describe-image.js +1 -1
- package/dist/shared/types/questions/spontaneous-qa.js +1 -1
- package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
- package/dist/shared/types/role.types.js +1 -1
- package/dist/shared/types/structure-management.js +1 -28
- package/dist/shared/types/student.types.js +1 -54
- package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
- package/package.json +4 -2
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from './FillMissingWordsInGridClient';
|
|
1
|
+
export*from"./FillMissingWordsInGridCreator";export*from"./FillMissingWordsInGridClient";
|
|
@@ -1,37 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
const sharedConfig = {
|
|
3
|
-
useRawInitialData: true,
|
|
4
|
-
getExtraProps: (ctx) => ({
|
|
5
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
6
|
-
}),
|
|
7
|
-
wrapOnSave: (data, ctx) => ({
|
|
8
|
-
type: ctx.questionType,
|
|
9
|
-
content: ctx.state.content,
|
|
10
|
-
points: data.points ?? ctx.state.points ?? 1,
|
|
11
|
-
level: ctx.state.level,
|
|
12
|
-
difficulty: ctx.state.difficulty,
|
|
13
|
-
skill: ctx.state.skill,
|
|
14
|
-
// Flatten content/answer from creator data
|
|
15
|
-
...(data.content !== undefined ? { content: data.content } : {}),
|
|
16
|
-
answer: data.answer ?? data,
|
|
17
|
-
explanation: data.explanation || '',
|
|
18
|
-
}),
|
|
19
|
-
wrapOnChange: (data, ctx) => ({
|
|
20
|
-
type: ctx.questionType,
|
|
21
|
-
points: data.points ?? ctx.state.points ?? 1,
|
|
22
|
-
level: ctx.state.level,
|
|
23
|
-
difficulty: ctx.state.difficulty,
|
|
24
|
-
skill: ctx.state.skill,
|
|
25
|
-
content: data.content,
|
|
26
|
-
answer: data.answer,
|
|
27
|
-
explanation: data.explanation || '',
|
|
28
|
-
}),
|
|
29
|
-
};
|
|
30
|
-
export const fillMissingWordsInGridRegistration = {
|
|
31
|
-
component: FillMissingWordsInGridCreator,
|
|
32
|
-
...sharedConfig,
|
|
33
|
-
};
|
|
34
|
-
export const writeMissingLettersRegistration = {
|
|
35
|
-
component: WriteMissingLettersCreator,
|
|
36
|
-
...sharedConfig,
|
|
37
|
-
};
|
|
1
|
+
import{FillMissingWordsInGridCreator as t,WriteMissingLettersCreator as n}from"./FillMissingWordsInGridCreator";const e={useRawInitialData:!0,getExtraProps:t=>({onUnsavedChangesChange:t.onUnsavedChangesChange}),wrapOnSave:(t,n)=>({type:n.questionType,content:n.state.content,points:t.points??n.state.points??1,level:n.state.level,difficulty:n.state.difficulty,skill:n.state.skill,...void 0!==t.content?{content:t.content}:{},answer:t.answer??t,explanation:t.explanation||""}),wrapOnChange:(t,n)=>({type:n.questionType,points:t.points??n.state.points??1,level:n.state.level,difficulty:n.state.difficulty,skill:n.state.skill,content:t.content,answer:t.answer,explanation:t.explanation||""})};export const fillMissingWordsInGridRegistration={component:t,...e};export const writeMissingLettersRegistration={component:n,...e};
|
|
@@ -1,15 +1 @@
|
|
|
1
|
-
export const transformFillMissingWordsInGrid =
|
|
2
|
-
const contentData = question.content;
|
|
3
|
-
const answerData = question.answer;
|
|
4
|
-
if (!contentData?.grid || !contentData?.rows || !contentData?.columns) {
|
|
5
|
-
return { apiContent: {}, apiCorrectAnswer: {} };
|
|
6
|
-
}
|
|
7
|
-
return {
|
|
8
|
-
apiContent: contentData,
|
|
9
|
-
apiCorrectAnswer: {
|
|
10
|
-
answers: answerData?.answers || {},
|
|
11
|
-
caseSensitive: answerData?.caseSensitive || false,
|
|
12
|
-
},
|
|
13
|
-
explanation: answerData?.explanation || '',
|
|
14
|
-
};
|
|
15
|
-
};
|
|
1
|
+
export const transformFillMissingWordsInGrid=n=>{const e=n.content,s=n.answer;return e?.grid&&e?.rows&&e?.columns?{apiContent:e,apiCorrectAnswer:{answers:s?.answers||{},caseSensitive:s?.caseSensitive||!1},explanation:s?.explanation||""}:{apiContent:{},apiCorrectAnswer:{}}};
|
|
@@ -1,262 +1 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useRef, useEffect, useState } from 'react';
|
|
4
|
-
import { useParams } from 'next/navigation';
|
|
5
|
-
import { Button } from '../../../../components/ui/button';
|
|
6
|
-
import { Label } from '../../../../components/ui/label';
|
|
7
|
-
import { Input } from '../../../../components/ui/input';
|
|
8
|
-
import { Textarea } from '../../../../components/ui/textarea';
|
|
9
|
-
import { PointsInput } from '../../../../components/ui/points-input';
|
|
10
|
-
import { Card, CardContent, CardHeader, CardTitle } from '../../../../components/ui/card';
|
|
11
|
-
import { useDebouncedCallback } from '../../../../shared/lib/hooks';
|
|
12
|
-
import { useReactHookForm } from '../../../../shared/lib/hooks/useReactHookForm';
|
|
13
|
-
import { usePresignedFileUrl } from '../../../../shared/lib/hooks/usePresignedFileUrl';
|
|
14
|
-
import { Mic, Type, Volume2, BookOpen, MessageSquareText, Info, Plus, Trash2, Loader2, Users, UserCheck, } from 'lucide-react';
|
|
15
|
-
import { toast } from 'sonner';
|
|
16
|
-
import { FileUpload } from '../../../../components/ui/file-upload';
|
|
17
|
-
import { CreatorGuide } from '../../components/CreatorGuide';
|
|
18
|
-
import { z } from 'zod';
|
|
19
|
-
// ─── Validation Schema ─────────────────────────────────────────────────────
|
|
20
|
-
const gnSpeakingInterviewFormSchema = z.object({
|
|
21
|
-
inputType: z.enum(['AUDIO', 'TEXT']),
|
|
22
|
-
audioUrl: z.string().optional(),
|
|
23
|
-
imageUrl: z.string().optional(),
|
|
24
|
-
questionText: z.string().optional(),
|
|
25
|
-
targetCandidate: z.enum(['VIRTUAL', 'REAL']),
|
|
26
|
-
virtualAnswerText: z.string().optional(),
|
|
27
|
-
virtualAnswerAudioUrl: z.string().optional(),
|
|
28
|
-
isExtendedResponse: z.boolean().optional(),
|
|
29
|
-
sampleAnswers: z.array(z.string()).optional(),
|
|
30
|
-
explanation: z.string().optional(),
|
|
31
|
-
points: z.number().min(0).default(1),
|
|
32
|
-
});
|
|
33
|
-
// ─── Main Creator Component ─────────────────────────────────────────────────
|
|
34
|
-
export function GNSpeakingInterviewCreator({ initialData, onSave, onChange, externalErrors, validationRef, }) {
|
|
35
|
-
const apiData = initialData;
|
|
36
|
-
const params = useParams();
|
|
37
|
-
const examRouteId = params?.examId || params?.id;
|
|
38
|
-
const initSampleAnswers = (() => {
|
|
39
|
-
const newArr = apiData?.expectedAnswers || apiData?.correctAnswer?.expectedAnswers;
|
|
40
|
-
if (Array.isArray(newArr) && newArr.length > 0)
|
|
41
|
-
return newArr;
|
|
42
|
-
const arr = apiData?.sampleAnswers || apiData?.content?.sampleAnswers;
|
|
43
|
-
if (Array.isArray(arr) && arr.length > 0)
|
|
44
|
-
return arr;
|
|
45
|
-
const single = apiData?.sampleAnswer || apiData?.correctAnswer?.sampleAnswer;
|
|
46
|
-
if (single)
|
|
47
|
-
return [single];
|
|
48
|
-
return [''];
|
|
49
|
-
})();
|
|
50
|
-
const form = useReactHookForm({
|
|
51
|
-
defaultValues: {
|
|
52
|
-
inputType: apiData?.inputType || apiData?.content?.inputType || 'TEXT',
|
|
53
|
-
audioUrl: apiData?.audioUrl || apiData?.content?.audioUrl || '',
|
|
54
|
-
imageUrl: apiData?.imageUrl || apiData?.content?.imageUrl || '',
|
|
55
|
-
questionText: apiData?.questionText || apiData?.content?.questionText || '',
|
|
56
|
-
targetCandidate: apiData?.targetCandidate || apiData?.content?.targetCandidate || 'REAL',
|
|
57
|
-
virtualAnswerText: apiData?.virtualAnswerText ||
|
|
58
|
-
apiData?.content?.virtualAnswer?.answerText ||
|
|
59
|
-
'',
|
|
60
|
-
virtualAnswerAudioUrl: apiData?.virtualAnswerAudioUrl ||
|
|
61
|
-
apiData?.content?.virtualAnswer?.audioUrl ||
|
|
62
|
-
'',
|
|
63
|
-
isExtendedResponse: apiData?.isExtendedResponse || apiData?.content?.isExtendedResponse || false,
|
|
64
|
-
sampleAnswers: initSampleAnswers,
|
|
65
|
-
explanation: apiData?.explanation || '',
|
|
66
|
-
points: apiData?.points ?? 1,
|
|
67
|
-
},
|
|
68
|
-
zodSchema: gnSpeakingInterviewFormSchema,
|
|
69
|
-
mode: 'onBlur',
|
|
70
|
-
});
|
|
71
|
-
const previousInitialDataRef = useRef(initialData);
|
|
72
|
-
const isInitializingRef = useRef(false);
|
|
73
|
-
const onChangeRef = useRef(onChange);
|
|
74
|
-
const previousPayloadRef = useRef('');
|
|
75
|
-
const [isGeneratingAudio, setIsGeneratingAudio] = useState(false);
|
|
76
|
-
const [isGeneratingVirtualAudio, setIsGeneratingVirtualAudio] = useState(false);
|
|
77
|
-
const [previewAudioUrl, setPreviewAudioUrl] = useState(null);
|
|
78
|
-
const [virtualPreviewAudioUrl, setVirtualPreviewAudioUrl] = useState(null);
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
onChangeRef.current = onChange;
|
|
81
|
-
}, [onChange]);
|
|
82
|
-
const debouncedOnChange = useDebouncedCallback((data) => {
|
|
83
|
-
onChangeRef.current?.(data);
|
|
84
|
-
}, 300);
|
|
85
|
-
const watchedInputType = form.watch('inputType');
|
|
86
|
-
const watchedAudioUrl = form.watch('audioUrl');
|
|
87
|
-
const watchedImageUrl = form.watch('imageUrl');
|
|
88
|
-
const { previewUrl: imagePreviewUrl, isLoading: isImagePreviewLoading } = usePresignedFileUrl(watchedImageUrl);
|
|
89
|
-
const watchedQuestionText = form.watch('questionText');
|
|
90
|
-
const watchedTargetCandidate = form.watch('targetCandidate');
|
|
91
|
-
const watchedVirtualAnswerText = form.watch('virtualAnswerText');
|
|
92
|
-
const watchedVirtualAnswerAudioUrl = form.watch('virtualAnswerAudioUrl');
|
|
93
|
-
const watchedIsExtendedResponse = form.watch('isExtendedResponse');
|
|
94
|
-
const watchedSampleAnswers = form.watch('sampleAnswers') || [''];
|
|
95
|
-
const watchedExplanation = form.watch('explanation');
|
|
96
|
-
const watchedPoints = form.watch('points');
|
|
97
|
-
// For the demo, we'll treat 'A' as virtual by default
|
|
98
|
-
// The actual virtualCandidate is set at group level, but in the creator we show
|
|
99
|
-
// conditional UI based on targetCandidate
|
|
100
|
-
const isVirtualQuestion = watchedTargetCandidate === 'VIRTUAL'; // VIRTUAL = virtual by default
|
|
101
|
-
// Auto-set points for Virtual candidate to 0, and revert to 1 if changed back
|
|
102
|
-
useEffect(() => {
|
|
103
|
-
if (isInitializingRef.current)
|
|
104
|
-
return;
|
|
105
|
-
if (isVirtualQuestion) {
|
|
106
|
-
if (watchedPoints !== 0) {
|
|
107
|
-
form.setValue('points', 0, { shouldValidate: true });
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
if (watchedPoints === 0) {
|
|
112
|
-
form.setValue('points', 1, { shouldValidate: true });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}, [isVirtualQuestion, watchedPoints, form]);
|
|
116
|
-
// Expose validation ref
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
if (validationRef) {
|
|
119
|
-
validationRef.current = {
|
|
120
|
-
triggerValidation: async () => await form.trigger(),
|
|
121
|
-
getFormData: () => form.getValues(),
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
}, [validationRef, form]);
|
|
125
|
-
// Reset form when initialData changes externally
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
const apiD = initialData;
|
|
128
|
-
const initialDataChanged = JSON.stringify(previousInitialDataRef.current) !== JSON.stringify(initialData);
|
|
129
|
-
if (!initialDataChanged)
|
|
130
|
-
return;
|
|
131
|
-
previousInitialDataRef.current = initialData;
|
|
132
|
-
const resetSampleAnswers = (() => {
|
|
133
|
-
const newArr = apiD?.expectedAnswers || apiD?.correctAnswer?.expectedAnswers;
|
|
134
|
-
if (Array.isArray(newArr) && newArr.length > 0)
|
|
135
|
-
return newArr;
|
|
136
|
-
const arr = apiD?.sampleAnswers || apiD?.content?.sampleAnswers;
|
|
137
|
-
if (Array.isArray(arr) && arr.length > 0)
|
|
138
|
-
return arr;
|
|
139
|
-
return [''];
|
|
140
|
-
})();
|
|
141
|
-
isInitializingRef.current = true;
|
|
142
|
-
form.reset({
|
|
143
|
-
inputType: apiD?.inputType || apiD?.content?.inputType || 'TEXT',
|
|
144
|
-
audioUrl: apiD?.audioUrl || apiD?.content?.audioUrl || '',
|
|
145
|
-
imageUrl: apiD?.imageUrl || apiD?.content?.imageUrl || '',
|
|
146
|
-
questionText: apiD?.questionText || apiD?.content?.questionText || '',
|
|
147
|
-
targetCandidate: apiD?.targetCandidate || apiD?.content?.targetCandidate || 'REAL',
|
|
148
|
-
virtualAnswerText: apiD?.virtualAnswerText || apiD?.content?.virtualAnswer?.answerText || '',
|
|
149
|
-
virtualAnswerAudioUrl: apiD?.virtualAnswerAudioUrl || apiD?.content?.virtualAnswer?.audioUrl || '',
|
|
150
|
-
isExtendedResponse: apiD?.isExtendedResponse || apiD?.content?.isExtendedResponse || false,
|
|
151
|
-
sampleAnswers: resetSampleAnswers,
|
|
152
|
-
explanation: apiD?.explanation || '',
|
|
153
|
-
points: apiD?.points ?? 1,
|
|
154
|
-
});
|
|
155
|
-
setTimeout(() => {
|
|
156
|
-
isInitializingRef.current = false;
|
|
157
|
-
}, 150);
|
|
158
|
-
}, [initialData, form]);
|
|
159
|
-
// Debounced onChange
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
if (isInitializingRef.current)
|
|
162
|
-
return;
|
|
163
|
-
const payload = {
|
|
164
|
-
inputType: watchedInputType,
|
|
165
|
-
audioUrl: watchedAudioUrl,
|
|
166
|
-
imageUrl: watchedImageUrl,
|
|
167
|
-
questionText: watchedQuestionText,
|
|
168
|
-
targetCandidate: watchedTargetCandidate,
|
|
169
|
-
virtualAnswerText: watchedVirtualAnswerText,
|
|
170
|
-
virtualAnswerAudioUrl: watchedVirtualAnswerAudioUrl,
|
|
171
|
-
isExtendedResponse: watchedIsExtendedResponse,
|
|
172
|
-
sampleAnswers: watchedSampleAnswers,
|
|
173
|
-
explanation: watchedExplanation,
|
|
174
|
-
points: watchedPoints,
|
|
175
|
-
};
|
|
176
|
-
const payloadString = JSON.stringify(payload);
|
|
177
|
-
if (previousPayloadRef.current !== payloadString) {
|
|
178
|
-
previousPayloadRef.current = payloadString;
|
|
179
|
-
debouncedOnChange(payload);
|
|
180
|
-
}
|
|
181
|
-
}, [
|
|
182
|
-
watchedInputType, watchedAudioUrl, watchedImageUrl, watchedQuestionText, watchedTargetCandidate,
|
|
183
|
-
watchedVirtualAnswerText, watchedVirtualAnswerAudioUrl, watchedIsExtendedResponse,
|
|
184
|
-
watchedSampleAnswers, watchedExplanation, watchedPoints, debouncedOnChange,
|
|
185
|
-
]);
|
|
186
|
-
// TTS handlers
|
|
187
|
-
const handleGenerateAudio = async () => {
|
|
188
|
-
const text = form.getValues('questionText');
|
|
189
|
-
if (!text?.trim()) {
|
|
190
|
-
toast.error('Vui lòng nhập nội dung câu hỏi trước khi tạo audio!');
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
try {
|
|
194
|
-
setIsGeneratingAudio(true);
|
|
195
|
-
toast.error('TTS chưa được tích hợp trong component SDK');
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
toast.error(error?.response?.data?.message || 'Lỗi khi tạo audio');
|
|
200
|
-
}
|
|
201
|
-
finally {
|
|
202
|
-
setIsGeneratingAudio(false);
|
|
203
|
-
}
|
|
204
|
-
};
|
|
205
|
-
const handleGenerateVirtualAudio = async () => {
|
|
206
|
-
const text = form.getValues('virtualAnswerText');
|
|
207
|
-
if (!text?.trim()) {
|
|
208
|
-
toast.error('Vui lòng nhập câu trả lời HS ảo trước khi tạo audio!');
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
try {
|
|
212
|
-
setIsGeneratingVirtualAudio(true);
|
|
213
|
-
toast.error('TTS chưa được tích hợp trong component SDK');
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
toast.error(error?.response?.data?.message || 'Lỗi khi tạo audio');
|
|
218
|
-
}
|
|
219
|
-
finally {
|
|
220
|
-
setIsGeneratingVirtualAudio(false);
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
// Dynamic expected answers
|
|
224
|
-
const handleAddSampleAnswer = () => {
|
|
225
|
-
form.setValue('sampleAnswers', [...watchedSampleAnswers, ''], { shouldValidate: false });
|
|
226
|
-
};
|
|
227
|
-
const handleRemoveSampleAnswer = (index) => {
|
|
228
|
-
const current = [...watchedSampleAnswers];
|
|
229
|
-
current.splice(index, 1);
|
|
230
|
-
if (current.length === 0)
|
|
231
|
-
current.push('');
|
|
232
|
-
form.setValue('sampleAnswers', current, { shouldValidate: true });
|
|
233
|
-
};
|
|
234
|
-
const handleSampleAnswerChange = (index, value) => {
|
|
235
|
-
const current = [...watchedSampleAnswers];
|
|
236
|
-
current[index] = value;
|
|
237
|
-
form.setValue('sampleAnswers', current, { shouldValidate: true });
|
|
238
|
-
};
|
|
239
|
-
return (_jsxs("div", { className: "space-y-6", children: [_jsx(CreatorGuide, { colorScheme: "blue", steps: [
|
|
240
|
-
{ text: 'Chọn đối tượng trả lời (Thí sinh ảo / Thí sinh thật).' },
|
|
241
|
-
{ text: 'Nhập câu hỏi của Giám khảo (Examiner Prompt) bằng Audio hoặc Text.', highlight: true },
|
|
242
|
-
{ text: 'Đối với Thí sinh ảo: cung cấp câu trả lời mô phỏng.' },
|
|
243
|
-
{ text: 'Đối với Thí sinh thật: thêm các đáp án mẫu (Sample Answers) để AI tham chiếu chấm điểm.' },
|
|
244
|
-
], tips: [
|
|
245
|
-
'Câu của Thí sinh ảo sẽ tự động phát và điểm số bằng 0.',
|
|
246
|
-
'Nên sử dụng TTS (Text-to-Speech) để tiết kiệm thời gian chuẩn bị audio.',
|
|
247
|
-
] }), _jsxs(Card, { className: "overflow-hidden border border-slate-200 bg-white shadow-sm", children: [_jsx(CardHeader, { className: "border-b border-slate-100 bg-slate-50/50 pb-4", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-lg bg-blue-100 text-blue-700", children: _jsx(Users, { className: "h-4 w-4" }) }), _jsx(CardTitle, { className: "text-lg font-semibold text-slate-800", children: "C\u1EA5u h\u00ECnh C\u00E2u h\u1ECFi & \u0110\u1ED1i t\u01B0\u1EE3ng" })] }) }), _jsxs(CardContent, { className: "p-0", children: [_jsxs("div", { className: "p-6 space-y-8", children: [_jsxs("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2", children: [_jsx("div", { className: "space-y-1", children: _jsx("div", { className: "w-full xl:w-2/3 animate-in fade-in zoom-in-95 duration-200", children: _jsx(PointsInput, { ...form.register('points', { valueAsNumber: true }), error: form.hasError('points') ? form.getFieldError('points') : undefined, disabled: isVirtualQuestion, allowZero: true }) }) }), _jsxs("div", { className: "space-y-3", children: [_jsxs(Label, { className: "text-sm font-semibold text-slate-700 flex items-center gap-2", children: [_jsx(UserCheck, { className: "h-4 w-4" }), "\u0110\u1ED1i t\u01B0\u1EE3ng tr\u1EA3 l\u1EDDi"] }), _jsxs("div", { className: "flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1", children: [_jsx("button", { type: "button", onClick: () => form.setValue('targetCandidate', 'VIRTUAL', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${isVirtualQuestion
|
|
248
|
-
? 'bg-white text-blue-700 shadow-sm'
|
|
249
|
-
: 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: "\uD83E\uDD16 Th\u00ED sinh \u1EA3o (Virtual)" }), _jsx("button", { type: "button", onClick: () => form.setValue('targetCandidate', 'REAL', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${!isVirtualQuestion
|
|
250
|
-
? 'bg-white text-emerald-700 shadow-sm'
|
|
251
|
-
: 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: "\uD83D\uDC66 Th\u00ED sinh th\u1EADt (Real)" })] })] })] }), _jsxs("div", { className: `flex items-start gap-3 rounded-lg border px-4 py-3 text-sm transition-colors duration-300 ${isVirtualQuestion
|
|
252
|
-
? 'border-blue-100 bg-blue-50/50 text-blue-800'
|
|
253
|
-
: 'border-emerald-100 bg-emerald-50/50 text-emerald-800'}`, children: [_jsx(Info, { className: `mt-0.5 h-4 w-4 shrink-0 ${isVirtualQuestion ? 'text-blue-500' : 'text-emerald-500'}` }), _jsx("p", { children: isVirtualQuestion ? (_jsxs("span", { children: [_jsx("strong", { children: "C\u1EA3nh b\u00E1o:" }), " C\u00E2u n\u00E0y d\u00E0nh cho ", _jsx("strong", { children: "Th\u00ED sinh \u1EA3o" }), ". H\u1EC7 th\u1ED1ng s\u1EBD b\u1ECF qua b\u01B0\u1EDBc thu \u00E2m v\u00E0 t\u1EF1 \u0111\u1ED9ng ph\u00E1t audio m\u00F4 ph\u1ECFng. Khi th\u00ED sinh thi, c\u00E2u n\u00E0y mang t\u00EDnh ch\u1EA5t giao ti\u1EBFp (\u0110i\u1EC3m m\u1EB7c \u0111\u1ECBnh = 0)."] })) : (_jsxs("span", { children: [_jsx("strong", { children: "L\u01B0u \u00FD:" }), " C\u00E2u n\u00E0y d\u00E0nh cho ", _jsx("strong", { children: "Th\u00ED sinh th\u1EADt" }), " l\u00E0m b\u00E0i. AI s\u1EBD thu \u00E2m v\u00E0 t\u1EF1 \u0111\u1ED9ng ch\u1EA5m \u0111i\u1EC3m d\u1EF1a b\u1EB1ng c\u00E1ch so s\u00E1nh v\u1EDBi c\u00E1c ", _jsx("strong", { children: "\u0110\u00E1p \u00E1n m\u1EABu" }), " b\u00EAn d\u01B0\u1EDBi."] })) })] }), _jsx("hr", { className: "border-slate-100" }), _jsxs("div", { className: "space-y-4", children: [_jsx(Label, { className: "block text-sm font-semibold text-slate-700", children: "C\u00E2u h\u1ECFi c\u1EE7a Gi\u00E1m kh\u1EA3o (Examiner Prompt)" }), _jsxs("div", { className: "flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1 mb-4", children: [_jsxs("button", { type: "button", onClick: () => form.setValue('inputType', 'AUDIO', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${watchedInputType === 'AUDIO'
|
|
254
|
-
? 'bg-white text-slate-800 shadow-sm'
|
|
255
|
-
: 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: [_jsx(Mic, { className: "h-3.5 w-3.5" }), " Upload Audio"] }), _jsxs("button", { type: "button", onClick: () => form.setValue('inputType', 'TEXT', { shouldValidate: true }), className: `flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all ${watchedInputType === 'TEXT'
|
|
256
|
-
? 'bg-white text-slate-800 shadow-sm'
|
|
257
|
-
: 'text-slate-500 hover:bg-slate-200/50 hover:text-slate-700'}`, children: [_jsx(Type, { className: "h-3.5 w-3.5" }), " Nh\u1EADp v\u0103n b\u1EA3n (TTS)"] })] }), _jsx("div", { className: "max-w-3xl", children: watchedInputType === 'AUDIO' ? (_jsx(FileUpload, { id: "audioUrl", label: "", accept: "audio/*", value: watchedAudioUrl || '', onChange: (url) => form.setValue('audioUrl', url, { shouldValidate: true }), maxSize: 20, placeholder: "T\u1EA3i l\u00EAn file audio c\u00E2u h\u1ECFi (MP3, WAV...)", autoUpload: true, prefix: "speaking/gn-interview" })) : (_jsxs("div", { className: "space-y-3", children: [_jsx(Input, { ...form.register('questionText'), placeholder: "VD: Do you work or are you a student?", className: "border-slate-200 focus:border-blue-400 focus:ring-blue-100" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs(Button, { type: "button", variant: "default", size: "sm", onClick: handleGenerateAudio, disabled: isGeneratingAudio || !watchedQuestionText, className: "gap-2 rounded-none bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold shadow-md shadow-blue-500/30 hover:shadow-lg hover:shadow-blue-500/40 hover:-translate-y-0.5 transition-all", children: [isGeneratingAudio ? (_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(Volume2, { className: "h-3.5 w-3.5" })), "T\u1EA1o Audio"] }), previewAudioUrl && (_jsx("audio", { controls: true, src: previewAudioUrl, className: "h-8 max-w-[250px]" }))] })] })) })] }), _jsx("hr", { className: "border-slate-100" }), _jsxs("div", { className: "space-y-4", children: [_jsx(Label, { className: "block text-sm font-semibold text-slate-700", children: "H\u00ECnh \u1EA3nh (Tu\u1EF3 ch\u1ECDn)" }), _jsxs("div", { className: "max-w-3xl", children: [_jsx(FileUpload, { id: "imageUrl", label: "", accept: "image/*", value: watchedImageUrl || '', onChange: (url) => form.setValue('imageUrl', url, { shouldValidate: true }), maxSize: 5, placeholder: "T\u1EA3i l\u00EAn h\u00ECnh \u1EA3nh tham kh\u1EA3o (JPG, PNG...)", autoUpload: true, prefix: "speaking/gn-interview" }), watchedImageUrl && (_jsx("div", { className: "mt-4", children: isImagePreviewLoading ? (_jsx("div", { className: "flex h-32 w-full max-w-[240px] items-center justify-center rounded-lg border border-slate-200 bg-slate-50", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : imagePreviewUrl ? (_jsx("div", { className: "relative inline-block overflow-hidden rounded-lg border border-slate-200 shadow-sm bg-slate-50 p-1", children: _jsx("img", { src: imagePreviewUrl, alt: "Preview", className: "max-h-48 object-contain rounded" }) })) : null }))] })] })] }), _jsx("div", { className: `border-t transition-colors duration-500 ${isVirtualQuestion ? 'border-blue-100 bg-blue-50/40' : 'border-emerald-100 bg-emerald-50/40'}`, children: _jsx("div", { className: "p-6", children: isVirtualQuestion ? (
|
|
258
|
-
/* Virtual Candidate Settings */
|
|
259
|
-
_jsxs("div", { className: "space-y-5 animate-in fade-in slide-in-from-bottom-2 duration-300", children: [_jsxs("h4", { className: "flex items-center gap-2 text-sm font-semibold text-blue-900", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded bg-blue-200/50 text-xs text-blue-700", children: "\uD83E\uDD16" }), "C\u1EA5u h\u00ECnh c\u00E2u tr\u1EA3 l\u1EDDi m\u00F4 ph\u1ECFng"] }), _jsxs("div", { className: "space-y-4 max-w-3xl", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { className: "text-sm font-medium text-slate-700", children: "N\u1ED9i dung c\u00E2u tr\u1EA3 l\u1EDDi (Text)" }), _jsx(Textarea, { value: watchedVirtualAnswerText || '', onChange: (e) => form.setValue('virtualAnswerText', e.target.value, { shouldValidate: true }), placeholder: "VD: My name is Lan. I am a student.", rows: 2, className: "resize-none border-slate-200 focus:border-blue-400 focus:ring-blue-100" })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { className: "text-sm font-medium text-slate-700", children: "Audio c\u00E2u tr\u1EA3 l\u1EDDi" }), _jsx(FileUpload, { id: "virtualAnswerAudioUrl", label: "", accept: "audio/*", value: watchedVirtualAnswerAudioUrl || '', onChange: (url) => form.setValue('virtualAnswerAudioUrl', url, { shouldValidate: true }), maxSize: 20, placeholder: "C\u00F3 th\u1EC3 upload audio ho\u1EB7c t\u1EA1o t\u1EF1 \u0111\u1ED9ng b\u1EB1ng TTS", autoUpload: true, prefix: "speaking/gn-interview/virtual" }), _jsxs("div", { className: "flex items-center gap-4 mt-2", children: [_jsxs(Button, { type: "button", variant: "default", size: "sm", onClick: handleGenerateVirtualAudio, disabled: isGeneratingVirtualAudio || !watchedVirtualAnswerText, className: "gap-2 rounded-none bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold shadow-md shadow-blue-500/30 hover:shadow-lg hover:shadow-blue-500/40 hover:-translate-y-0.5 transition-all", children: [isGeneratingVirtualAudio ? (_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(Volume2, { className: "h-3.5 w-3.5" })), "T\u1EA1o Audio t\u1EEB Text"] }), virtualPreviewAudioUrl && (_jsx("audio", { controls: true, src: virtualPreviewAudioUrl, className: "h-8 max-w-[250px]" }))] })] })] })] })) : (
|
|
260
|
-
/* Real Candidate Sample Answers */
|
|
261
|
-
_jsxs("div", { className: "space-y-5 animate-in fade-in slide-in-from-bottom-2 duration-300", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsxs("h4", { className: "flex items-center gap-2 text-sm font-semibold text-emerald-900", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded bg-emerald-200/50 text-emerald-700", children: _jsx(MessageSquareText, { className: "h-3.5 w-3.5" }) }), "\u0110\u00E1p \u00E1n m\u1EABu tham chi\u1EBFu (Sample Answers)"] }) }), _jsxs("div", { className: "space-y-3 max-w-4xl", children: [watchedSampleAnswers.map((answer, index) => (_jsxs("div", { className: "flex items-start gap-3", children: [_jsx("div", { className: "mt-1.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-emerald-100 text-xs font-semibold text-emerald-700", children: index + 1 }), _jsx(Textarea, { value: answer || '', onChange: (e) => handleSampleAnswerChange(index, e.target.value), placeholder: `VD: ${index === 0 ? 'I am a student. I study at a university.' : 'No, I currently work.'}`, rows: 2, className: "flex-1 resize-none border-slate-200 bg-white focus:border-emerald-400 focus:ring-emerald-100" }), watchedSampleAnswers.length > 1 && (_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "mt-1 h-8 w-8 shrink-0 text-slate-400 hover:bg-red-50 hover:text-red-600", onClick: () => handleRemoveSampleAnswer(index), children: _jsx(Trash2, { className: "h-4 w-4" }) }))] }, index))), _jsx("div", { className: "pt-2", children: _jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "gap-1.5 border-emerald-200 bg-emerald-50/50 text-emerald-700 hover:bg-emerald-100 hover:text-emerald-800", onClick: handleAddSampleAnswer, children: [_jsx(Plus, { className: "h-4 w-4" }), " Th\u00EAm \u0111\u00E1p \u00E1n m\u1EABu"] }) })] })] })) }) }), externalErrors && externalErrors.length > 0 && (_jsx("div", { className: "border-t border-red-100 bg-red-50 p-6", children: _jsx("div", { className: "flex items-start gap-3", children: _jsxs("div", { className: "text-sm", children: [_jsx("p", { className: "font-semibold text-red-800", children: "C\u1EA7n ph\u1EA3i s\u1EEDa c\u00E1c l\u1ED7i sau:" }), _jsx("ul", { className: "mt-1 list-inside list-disc text-red-700", children: externalErrors.map((error, index) => (_jsx("li", { children: error }, index))) })] }) }) }))] })] }), _jsxs(Card, { className: "border border-slate-200 bg-slate-50/50 shadow-sm", children: [_jsx(CardHeader, { className: "pb-3", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-slate-200/50 text-slate-600", children: _jsx(BookOpen, { className: "h-3.5 w-3.5" }) }), _jsx(CardTitle, { className: "text-base font-semibold text-slate-800", children: "Gi\u1EA3i th\u00EDch chi ti\u1EBFt" })] }), _jsx("span", { className: "text-xs font-medium text-slate-500 uppercase tracking-wider", children: "T\u00F9y ch\u1ECDn" })] }) }), _jsx(CardContent, { children: _jsx(Textarea, { value: watchedExplanation || '', onChange: (e) => form.setValue('explanation', e.target.value, { shouldValidate: true }), placeholder: "Ghi ch\u00FA th\u00EAm v\u1EC1 ti\u00EAu ch\u00ED ch\u1EA5m \u0111i\u1EC3m, l\u01B0u \u00FD cho gi\u00E1m kh\u1EA3o...", rows: 2, className: "resize-none border-slate-200 bg-white focus:border-slate-300 focus:ring-slate-100" }) })] })] }));
|
|
262
|
-
}
|
|
1
|
+
"use client";import{jsx as e,jsxs as t}from"react/jsx-runtime";import{useRef as a,useEffect as s,useState as r}from"react";import{useParams as n}from"next/navigation";import{Button as i}from"../../../../components/ui/button";import{Label as l}from"../../../../components/ui/label";import{Input as o}from"../../../../components/ui/input";import{Textarea as d}from"../../../../components/ui/textarea";import{PointsInput as c}from"../../../../components/ui/points-input";import{Card as m,CardContent as h,CardHeader as u,CardTitle as p}from"../../../../components/ui/card";import{useDebouncedCallback as g}from"../../../../shared/lib/hooks";import{useReactHookForm as x}from"../../../../shared/lib/hooks/useReactHookForm";import{usePresignedFileUrl as b}from"../../../../shared/lib/hooks/usePresignedFileUrl";import{Mic as w,Type as v,Volume2 as f,BookOpen as N,MessageSquareText as A,Info as T,Plus as y,Trash2 as U,Loader2 as V,Users as C,UserCheck as k}from"lucide-react";import{toast as E}from"sonner";import{FileUpload as S}from"../../../../components/ui/file-upload";import{CreatorGuide as R}from"../../components/CreatorGuide";import{z as I}from"zod";const z=I.object({inputType:I.enum(["AUDIO","TEXT"]),audioUrl:I.string().optional(),imageUrl:I.string().optional(),questionText:I.string().optional(),targetCandidate:I.enum(["VIRTUAL","REAL"]),virtualAnswerText:I.string().optional(),virtualAnswerAudioUrl:I.string().optional(),isExtendedResponse:I.boolean().optional(),sampleAnswers:I.array(I.string()).optional(),explanation:I.string().optional(),points:I.number().min(0).default(1)});export function GNSpeakingInterviewCreator({initialData:I,onSave:j,onChange:q,externalErrors:D,validationRef:L}){const G=I,O=(n(),(()=>{const e=G?.expectedAnswers||G?.correctAnswer?.expectedAnswers;if(Array.isArray(e)&&e.length>0)return e;const t=G?.sampleAnswers||G?.content?.sampleAnswers;if(Array.isArray(t)&&t.length>0)return t;const a=G?.sampleAnswer||G?.correctAnswer?.sampleAnswer;return a?[a]:[""]})()),P=x({defaultValues:{inputType:G?.inputType||G?.content?.inputType||"TEXT",audioUrl:G?.audioUrl||G?.content?.audioUrl||"",imageUrl:G?.imageUrl||G?.content?.imageUrl||"",questionText:G?.questionText||G?.content?.questionText||"",targetCandidate:G?.targetCandidate||G?.content?.targetCandidate||"REAL",virtualAnswerText:G?.virtualAnswerText||G?.content?.virtualAnswer?.answerText||"",virtualAnswerAudioUrl:G?.virtualAnswerAudioUrl||G?.content?.virtualAnswer?.audioUrl||"",isExtendedResponse:G?.isExtendedResponse||G?.content?.isExtendedResponse||!1,sampleAnswers:O,explanation:G?.explanation||"",points:G?.points??1},zodSchema:z,mode:"onBlur"}),X=a(I),F=a(!1),H=a(q),J=a(""),[K,M]=r(!1),[B,W]=r(!1),[Z,Q]=r(null),[Y,$]=r(null);s(()=>{H.current=q},[q]);const _=g(e=>{H.current?.(e)},300),ee=P.watch("inputType"),te=P.watch("audioUrl"),ae=P.watch("imageUrl"),{previewUrl:se,isLoading:re}=b(ae),ne=P.watch("questionText"),ie=P.watch("targetCandidate"),le=P.watch("virtualAnswerText"),oe=P.watch("virtualAnswerAudioUrl"),de=P.watch("isExtendedResponse"),ce=P.watch("sampleAnswers")||[""],me=P.watch("explanation"),he=P.watch("points"),ue="VIRTUAL"===ie;s(()=>{F.current||(ue?0!==he&&P.setValue("points",0,{shouldValidate:!0}):0===he&&P.setValue("points",1,{shouldValidate:!0}))},[ue,he,P]),s(()=>{L&&(L.current={triggerValidation:async()=>await P.trigger(),getFormData:()=>P.getValues()})},[L,P]),s(()=>{const e=I;if(!(JSON.stringify(X.current)!==JSON.stringify(I)))return;X.current=I;const t=(()=>{const t=e?.expectedAnswers||e?.correctAnswer?.expectedAnswers;if(Array.isArray(t)&&t.length>0)return t;const a=e?.sampleAnswers||e?.content?.sampleAnswers;return Array.isArray(a)&&a.length>0?a:[""]})();F.current=!0,P.reset({inputType:e?.inputType||e?.content?.inputType||"TEXT",audioUrl:e?.audioUrl||e?.content?.audioUrl||"",imageUrl:e?.imageUrl||e?.content?.imageUrl||"",questionText:e?.questionText||e?.content?.questionText||"",targetCandidate:e?.targetCandidate||e?.content?.targetCandidate||"REAL",virtualAnswerText:e?.virtualAnswerText||e?.content?.virtualAnswer?.answerText||"",virtualAnswerAudioUrl:e?.virtualAnswerAudioUrl||e?.content?.virtualAnswer?.audioUrl||"",isExtendedResponse:e?.isExtendedResponse||e?.content?.isExtendedResponse||!1,sampleAnswers:t,explanation:e?.explanation||"",points:e?.points??1}),setTimeout(()=>{F.current=!1},150)},[I,P]),s(()=>{if(F.current)return;const e={inputType:ee,audioUrl:te,imageUrl:ae,questionText:ne,targetCandidate:ie,virtualAnswerText:le,virtualAnswerAudioUrl:oe,isExtendedResponse:de,sampleAnswers:ce,explanation:me,points:he},t=JSON.stringify(e);J.current!==t&&(J.current=t,_(e))},[ee,te,ae,ne,ie,le,oe,de,ce,me,he,_]);return t("div",{className:"space-y-6",children:[e(R,{colorScheme:"blue",steps:[{text:"Chọn đối tượng trả lời (Thí sinh ảo / Thí sinh thật)."},{text:"Nhập câu hỏi của Giám khảo (Examiner Prompt) bằng Audio hoặc Text.",highlight:!0},{text:"Đối với Thí sinh ảo: cung cấp câu trả lời mô phỏng."},{text:"Đối với Thí sinh thật: thêm các đáp án mẫu (Sample Answers) để AI tham chiếu chấm điểm."}],tips:["Câu của Thí sinh ảo sẽ tự động phát và điểm số bằng 0.","Nên sử dụng TTS (Text-to-Speech) để tiết kiệm thời gian chuẩn bị audio."]}),t(m,{className:"overflow-hidden border border-slate-200 bg-white shadow-sm",children:[e(u,{className:"border-b border-slate-100 bg-slate-50/50 pb-4",children:t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-7 w-7 items-center justify-center rounded-lg bg-blue-100 text-blue-700",children:e(C,{className:"h-4 w-4"})}),e(p,{className:"text-lg font-semibold text-slate-800",children:"Cấu hình Câu hỏi & Đối tượng"})]})}),t(h,{className:"p-0",children:[t("div",{className:"p-6 space-y-8",children:[t("div",{className:"grid grid-cols-1 gap-6 md:grid-cols-2",children:[e("div",{className:"space-y-1",children:e("div",{className:"w-full xl:w-2/3 animate-in fade-in zoom-in-95 duration-200",children:e(c,{...P.register("points",{valueAsNumber:!0}),error:P.hasError("points")?P.getFieldError("points"):void 0,disabled:ue,allowZero:!0})})}),t("div",{className:"space-y-3",children:[t(l,{className:"text-sm font-semibold text-slate-700 flex items-center gap-2",children:[e(k,{className:"h-4 w-4"}),"Đối tượng trả lời"]}),t("div",{className:"flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1",children:[e("button",{type:"button",onClick:()=>P.setValue("targetCandidate","VIRTUAL",{shouldValidate:!0}),className:"flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all "+(ue?"bg-white text-blue-700 shadow-sm":"text-slate-500 hover:bg-slate-200/50 hover:text-slate-700"),children:"🤖 Thí sinh ảo (Virtual)"}),e("button",{type:"button",onClick:()=>P.setValue("targetCandidate","REAL",{shouldValidate:!0}),className:"flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all "+(ue?"text-slate-500 hover:bg-slate-200/50 hover:text-slate-700":"bg-white text-emerald-700 shadow-sm"),children:"👦 Thí sinh thật (Real)"})]})]})]}),t("div",{className:"flex items-start gap-3 rounded-lg border px-4 py-3 text-sm transition-colors duration-300 "+(ue?"border-blue-100 bg-blue-50/50 text-blue-800":"border-emerald-100 bg-emerald-50/50 text-emerald-800"),children:[e(T,{className:"mt-0.5 h-4 w-4 shrink-0 "+(ue?"text-blue-500":"text-emerald-500")}),e("p",{children:t("span",ue?{children:[e("strong",{children:"Cảnh báo:"})," Câu này dành cho ",e("strong",{children:"Thí sinh ảo"}),". Hệ thống sẽ bỏ qua bước thu âm và tự động phát audio mô phỏng. Khi thí sinh thi, câu này mang tính chất giao tiếp (Điểm mặc định = 0)."]}:{children:[e("strong",{children:"Lưu ý:"})," Câu này dành cho ",e("strong",{children:"Thí sinh thật"})," làm bài. AI sẽ thu âm và tự động chấm điểm dựa bằng cách so sánh với các ",e("strong",{children:"Đáp án mẫu"})," bên dưới."]})})]}),e("hr",{className:"border-slate-100"}),t("div",{className:"space-y-4",children:[e(l,{className:"block text-sm font-semibold text-slate-700",children:"Câu hỏi của Giám khảo (Examiner Prompt)"}),t("div",{className:"flex w-fit rounded-lg border border-slate-200 bg-slate-100 p-1 mb-4",children:[t("button",{type:"button",onClick:()=>P.setValue("inputType","AUDIO",{shouldValidate:!0}),className:"flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all "+("AUDIO"===ee?"bg-white text-slate-800 shadow-sm":"text-slate-500 hover:bg-slate-200/50 hover:text-slate-700"),children:[e(w,{className:"h-3.5 w-3.5"})," Upload Audio"]}),t("button",{type:"button",onClick:()=>P.setValue("inputType","TEXT",{shouldValidate:!0}),className:"flex items-center gap-2 rounded-md px-4 py-1.5 text-sm font-medium transition-all "+("TEXT"===ee?"bg-white text-slate-800 shadow-sm":"text-slate-500 hover:bg-slate-200/50 hover:text-slate-700"),children:[e(v,{className:"h-3.5 w-3.5"})," Nhập văn bản (TTS)"]})]}),e("div",{className:"max-w-3xl",children:"AUDIO"===ee?e(S,{id:"audioUrl",label:"",accept:"audio/*",value:te||"",onChange:e=>P.setValue("audioUrl",e,{shouldValidate:!0}),maxSize:20,placeholder:"Tải lên file audio câu hỏi (MP3, WAV...)",autoUpload:!0,prefix:"speaking/gn-interview"}):t("div",{className:"space-y-3",children:[e(o,{...P.register("questionText"),placeholder:"VD: Do you work or are you a student?",className:"border-slate-200 focus:border-blue-400 focus:ring-blue-100"}),t("div",{className:"flex items-center gap-4",children:[t(i,{type:"button",variant:"default",size:"sm",onClick:async()=>{const e=P.getValues("questionText");if(e?.trim())try{return M(!0),void E.error("TTS chưa được tích hợp trong component SDK")}catch(e){E.error(e?.response?.data?.message||"Lỗi khi tạo audio")}finally{M(!1)}else E.error("Vui lòng nhập nội dung câu hỏi trước khi tạo audio!")},disabled:K||!ne,className:"gap-2 rounded-none bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold shadow-md shadow-blue-500/30 hover:shadow-lg hover:shadow-blue-500/40 hover:-translate-y-0.5 transition-all",children:[K?e(V,{className:"h-3.5 w-3.5 animate-spin"}):e(f,{className:"h-3.5 w-3.5"}),"Tạo Audio"]}),Z&&e("audio",{controls:!0,src:Z,className:"h-8 max-w-[250px]"})]})]})})]}),e("hr",{className:"border-slate-100"}),t("div",{className:"space-y-4",children:[e(l,{className:"block text-sm font-semibold text-slate-700",children:"Hình ảnh (Tuỳ chọn)"}),t("div",{className:"max-w-3xl",children:[e(S,{id:"imageUrl",label:"",accept:"image/*",value:ae||"",onChange:e=>P.setValue("imageUrl",e,{shouldValidate:!0}),maxSize:5,placeholder:"Tải lên hình ảnh tham khảo (JPG, PNG...)",autoUpload:!0,prefix:"speaking/gn-interview"}),ae&&e("div",{className:"mt-4",children:re?e("div",{className:"flex h-32 w-full max-w-[240px] items-center justify-center rounded-lg border border-slate-200 bg-slate-50",children:e(V,{className:"h-5 w-5 animate-spin text-slate-400"})}):se?e("div",{className:"relative inline-block overflow-hidden rounded-lg border border-slate-200 shadow-sm bg-slate-50 p-1",children:e("img",{src:se,alt:"Preview",className:"max-h-48 object-contain rounded"})}):null})]})]})]}),e("div",{className:"border-t transition-colors duration-500 "+(ue?"border-blue-100 bg-blue-50/40":"border-emerald-100 bg-emerald-50/40"),children:e("div",{className:"p-6",children:t("div",ue?{className:"space-y-5 animate-in fade-in slide-in-from-bottom-2 duration-300",children:[t("h4",{className:"flex items-center gap-2 text-sm font-semibold text-blue-900",children:[e("span",{className:"flex h-6 w-6 items-center justify-center rounded bg-blue-200/50 text-xs text-blue-700",children:"🤖"}),"Cấu hình câu trả lời mô phỏng"]}),t("div",{className:"space-y-4 max-w-3xl",children:[t("div",{className:"space-y-2",children:[e(l,{className:"text-sm font-medium text-slate-700",children:"Nội dung câu trả lời (Text)"}),e(d,{value:le||"",onChange:e=>P.setValue("virtualAnswerText",e.target.value,{shouldValidate:!0}),placeholder:"VD: My name is Lan. I am a student.",rows:2,className:"resize-none border-slate-200 focus:border-blue-400 focus:ring-blue-100"})]}),t("div",{className:"space-y-2",children:[e(l,{className:"text-sm font-medium text-slate-700",children:"Audio câu trả lời"}),e(S,{id:"virtualAnswerAudioUrl",label:"",accept:"audio/*",value:oe||"",onChange:e=>P.setValue("virtualAnswerAudioUrl",e,{shouldValidate:!0}),maxSize:20,placeholder:"Có thể upload audio hoặc tạo tự động bằng TTS",autoUpload:!0,prefix:"speaking/gn-interview/virtual"}),t("div",{className:"flex items-center gap-4 mt-2",children:[t(i,{type:"button",variant:"default",size:"sm",onClick:async()=>{const e=P.getValues("virtualAnswerText");if(e?.trim())try{return W(!0),void E.error("TTS chưa được tích hợp trong component SDK")}catch(e){E.error(e?.response?.data?.message||"Lỗi khi tạo audio")}finally{W(!1)}else E.error("Vui lòng nhập câu trả lời HS ảo trước khi tạo audio!")},disabled:B||!le,className:"gap-2 rounded-none bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold shadow-md shadow-blue-500/30 hover:shadow-lg hover:shadow-blue-500/40 hover:-translate-y-0.5 transition-all",children:[B?e(V,{className:"h-3.5 w-3.5 animate-spin"}):e(f,{className:"h-3.5 w-3.5"}),"Tạo Audio từ Text"]}),Y&&e("audio",{controls:!0,src:Y,className:"h-8 max-w-[250px]"})]})]})]})]}:{className:"space-y-5 animate-in fade-in slide-in-from-bottom-2 duration-300",children:[e("div",{className:"flex items-center gap-2",children:t("h4",{className:"flex items-center gap-2 text-sm font-semibold text-emerald-900",children:[e("span",{className:"flex h-6 w-6 items-center justify-center rounded bg-emerald-200/50 text-emerald-700",children:e(A,{className:"h-3.5 w-3.5"})}),"Đáp án mẫu tham chiếu (Sample Answers)"]})}),t("div",{className:"space-y-3 max-w-4xl",children:[ce.map((a,s)=>t("div",{className:"flex items-start gap-3",children:[e("div",{className:"mt-1.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-emerald-100 text-xs font-semibold text-emerald-700",children:s+1}),e(d,{value:a||"",onChange:e=>((e,t)=>{const a=[...ce];a[e]=t,P.setValue("sampleAnswers",a,{shouldValidate:!0})})(s,e.target.value),placeholder:"VD: "+(0===s?"I am a student. I study at a university.":"No, I currently work."),rows:2,className:"flex-1 resize-none border-slate-200 bg-white focus:border-emerald-400 focus:ring-emerald-100"}),ce.length>1&&e(i,{type:"button",variant:"ghost",size:"icon",className:"mt-1 h-8 w-8 shrink-0 text-slate-400 hover:bg-red-50 hover:text-red-600",onClick:()=>(e=>{const t=[...ce];t.splice(e,1),0===t.length&&t.push(""),P.setValue("sampleAnswers",t,{shouldValidate:!0})})(s),children:e(U,{className:"h-4 w-4"})})]},s)),e("div",{className:"pt-2",children:t(i,{type:"button",variant:"outline",size:"sm",className:"gap-1.5 border-emerald-200 bg-emerald-50/50 text-emerald-700 hover:bg-emerald-100 hover:text-emerald-800",onClick:()=>{P.setValue("sampleAnswers",[...ce,""],{shouldValidate:!1})},children:[e(y,{className:"h-4 w-4"})," Thêm đáp án mẫu"]})})]})]})})}),D&&D.length>0&&e("div",{className:"border-t border-red-100 bg-red-50 p-6",children:e("div",{className:"flex items-start gap-3",children:t("div",{className:"text-sm",children:[e("p",{className:"font-semibold text-red-800",children:"Cần phải sửa các lỗi sau:"}),e("ul",{className:"mt-1 list-inside list-disc text-red-700",children:D.map((t,a)=>e("li",{children:t},a))})]})})})]})]}),t(m,{className:"border border-slate-200 bg-slate-50/50 shadow-sm",children:[e(u,{className:"pb-3",children:t("div",{className:"flex items-center justify-between",children:[t("div",{className:"flex items-center gap-2",children:[e("div",{className:"flex h-6 w-6 items-center justify-center rounded-md bg-slate-200/50 text-slate-600",children:e(N,{className:"h-3.5 w-3.5"})}),e(p,{className:"text-base font-semibold text-slate-800",children:"Giải thích chi tiết"})]}),e("span",{className:"text-xs font-medium text-slate-500 uppercase tracking-wider",children:"Tùy chọn"})]})}),e(h,{children:e(d,{value:me||"",onChange:e=>P.setValue("explanation",e.target.value,{shouldValidate:!0}),placeholder:"Ghi chú thêm về tiêu chí chấm điểm, lưu ý cho giám khảo...",rows:2,className:"resize-none border-slate-200 bg-white focus:border-slate-300 focus:ring-slate-100"})})]})]})}
|
|
@@ -1,121 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
export const gnSpeakingInterviewRegistration = {
|
|
3
|
-
component: GNSpeakingInterviewCreator,
|
|
4
|
-
transformInitialData: (initialData) => {
|
|
5
|
-
const content = initialData?.content;
|
|
6
|
-
const answer = initialData?.answer || initialData?.correctAnswer || initialData?.correct_answer;
|
|
7
|
-
const points = initialData?.total_points !== undefined && initialData?.total_points !== null
|
|
8
|
-
? parseFloat(initialData.total_points)
|
|
9
|
-
: (initialData?.points ?? content?.points ?? answer?.points ?? 1);
|
|
10
|
-
const explanation = initialData?.explanation || content?.explanation || answer?.explanation || '';
|
|
11
|
-
// Normalize expected answers
|
|
12
|
-
const normalizeSampleAnswers = () => {
|
|
13
|
-
const newArr = answer?.expectedAnswers;
|
|
14
|
-
if (Array.isArray(newArr) && newArr.length > 0)
|
|
15
|
-
return newArr;
|
|
16
|
-
const arr = answer?.sampleAnswers;
|
|
17
|
-
if (Array.isArray(arr) && arr.length > 0)
|
|
18
|
-
return arr;
|
|
19
|
-
const single = answer?.sampleAnswer;
|
|
20
|
-
if (single)
|
|
21
|
-
return [single];
|
|
22
|
-
return [''];
|
|
23
|
-
};
|
|
24
|
-
if (content && typeof content === 'object' && content.targetCandidate !== undefined) {
|
|
25
|
-
return {
|
|
26
|
-
inputType: content.inputType || 'TEXT',
|
|
27
|
-
audioUrl: content.audioUrl || '',
|
|
28
|
-
imageUrl: content.imageUrl || '',
|
|
29
|
-
questionText: content.questionText || '',
|
|
30
|
-
targetCandidate: content.targetCandidate || 'REAL',
|
|
31
|
-
virtualAnswerText: content.virtualAnswer?.answerText || '',
|
|
32
|
-
virtualAnswerAudioUrl: content.virtualAnswer?.audioUrl || '',
|
|
33
|
-
isExtendedResponse: content.isExtendedResponse || false,
|
|
34
|
-
sampleAnswers: normalizeSampleAnswers(),
|
|
35
|
-
explanation,
|
|
36
|
-
points,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
return {
|
|
40
|
-
inputType: answer?.inputType || content?.inputType || 'TEXT',
|
|
41
|
-
audioUrl: answer?.audioUrl || content?.audioUrl || '',
|
|
42
|
-
imageUrl: answer?.imageUrl || content?.imageUrl || '',
|
|
43
|
-
questionText: answer?.questionText || content?.questionText || '',
|
|
44
|
-
targetCandidate: answer?.targetCandidate || content?.targetCandidate || 'REAL',
|
|
45
|
-
virtualAnswerText: answer?.virtualAnswerText || content?.virtualAnswer?.answerText || '',
|
|
46
|
-
virtualAnswerAudioUrl: answer?.virtualAnswerAudioUrl || content?.virtualAnswer?.audioUrl || '',
|
|
47
|
-
isExtendedResponse: answer?.isExtendedResponse || content?.isExtendedResponse || false,
|
|
48
|
-
sampleAnswers: normalizeSampleAnswers(),
|
|
49
|
-
explanation,
|
|
50
|
-
points,
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
getExtraProps: (ctx) => ({
|
|
54
|
-
onUnsavedChangesChange: ctx.onUnsavedChangesChange,
|
|
55
|
-
validationRef: ctx.validationRef,
|
|
56
|
-
}),
|
|
57
|
-
wrapOnSave: (data, ctx) => {
|
|
58
|
-
const expectedAnswers = (data.sampleAnswers || []).filter((s) => s && s.trim() !== '');
|
|
59
|
-
const isVirtual = data.targetCandidate === 'VIRTUAL'; // VIRTUAL = virtual by default
|
|
60
|
-
return {
|
|
61
|
-
type: ctx.questionType,
|
|
62
|
-
content: {
|
|
63
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null
|
|
64
|
-
? ctx.state.content
|
|
65
|
-
: {}),
|
|
66
|
-
inputType: data.inputType,
|
|
67
|
-
audioUrl: data.audioUrl || '',
|
|
68
|
-
imageUrl: data.imageUrl || '',
|
|
69
|
-
questionText: data.inputType === 'TEXT' ? data.questionText : undefined,
|
|
70
|
-
targetCandidate: data.targetCandidate || 'REAL',
|
|
71
|
-
virtualAnswer: isVirtual
|
|
72
|
-
? {
|
|
73
|
-
answerText: data.virtualAnswerText || '',
|
|
74
|
-
audioUrl: data.virtualAnswerAudioUrl || '',
|
|
75
|
-
}
|
|
76
|
-
: undefined,
|
|
77
|
-
isExtendedResponse: data.isExtendedResponse || false,
|
|
78
|
-
},
|
|
79
|
-
points: isVirtual ? 0 : (data?.points ?? ctx.state.points ?? 1),
|
|
80
|
-
level: ctx.state.level,
|
|
81
|
-
difficulty: ctx.state.difficulty,
|
|
82
|
-
skill: ctx.state.skill,
|
|
83
|
-
answer: {
|
|
84
|
-
expectedAnswers: isVirtual ? [] : expectedAnswers,
|
|
85
|
-
},
|
|
86
|
-
explanation: data?.explanation || '',
|
|
87
|
-
};
|
|
88
|
-
},
|
|
89
|
-
wrapOnChange: (data, ctx) => {
|
|
90
|
-
const expectedAnswers = data.sampleAnswers || [''];
|
|
91
|
-
const isVirtual = data.targetCandidate === 'VIRTUAL';
|
|
92
|
-
return {
|
|
93
|
-
type: ctx.questionType,
|
|
94
|
-
content: {
|
|
95
|
-
...(typeof ctx.state.content === 'object' && ctx.state.content !== null
|
|
96
|
-
? ctx.state.content
|
|
97
|
-
: {}),
|
|
98
|
-
inputType: data.inputType,
|
|
99
|
-
audioUrl: data.audioUrl || '',
|
|
100
|
-
imageUrl: data.imageUrl || '',
|
|
101
|
-
questionText: data.inputType === 'TEXT' ? data.questionText : undefined,
|
|
102
|
-
targetCandidate: data.targetCandidate || 'REAL',
|
|
103
|
-
virtualAnswer: isVirtual
|
|
104
|
-
? {
|
|
105
|
-
answerText: data.virtualAnswerText || '',
|
|
106
|
-
audioUrl: data.virtualAnswerAudioUrl || '',
|
|
107
|
-
}
|
|
108
|
-
: undefined,
|
|
109
|
-
isExtendedResponse: data.isExtendedResponse || false,
|
|
110
|
-
},
|
|
111
|
-
points: isVirtual ? 0 : (data?.points ?? ctx.state.points ?? 1),
|
|
112
|
-
level: ctx.state.level,
|
|
113
|
-
difficulty: ctx.state.difficulty,
|
|
114
|
-
skill: ctx.state.skill,
|
|
115
|
-
answer: {
|
|
116
|
-
expectedAnswers: isVirtual ? [] : expectedAnswers,
|
|
117
|
-
},
|
|
118
|
-
explanation: data?.explanation || '',
|
|
119
|
-
};
|
|
120
|
-
},
|
|
121
|
-
};
|
|
1
|
+
import{GNSpeakingInterviewCreator as e}from"./GNSpeakingInterviewCreator";export const gnSpeakingInterviewRegistration={component:e,transformInitialData:e=>{const t=e?.content,n=e?.answer||e?.correctAnswer||e?.correct_answer,a=null!=e?.total_points?parseFloat(e.total_points):e?.points??t?.points??n?.points??1,i=e?.explanation||t?.explanation||n?.explanation||"",s=()=>{const e=n?.expectedAnswers;if(Array.isArray(e)&&e.length>0)return e;const t=n?.sampleAnswers;if(Array.isArray(t)&&t.length>0)return t;const a=n?.sampleAnswer;return a?[a]:[""]};return t&&"object"==typeof t&&void 0!==t.targetCandidate?{inputType:t.inputType||"TEXT",audioUrl:t.audioUrl||"",imageUrl:t.imageUrl||"",questionText:t.questionText||"",targetCandidate:t.targetCandidate||"REAL",virtualAnswerText:t.virtualAnswer?.answerText||"",virtualAnswerAudioUrl:t.virtualAnswer?.audioUrl||"",isExtendedResponse:t.isExtendedResponse||!1,sampleAnswers:s(),explanation:i,points:a}:{inputType:n?.inputType||t?.inputType||"TEXT",audioUrl:n?.audioUrl||t?.audioUrl||"",imageUrl:n?.imageUrl||t?.imageUrl||"",questionText:n?.questionText||t?.questionText||"",targetCandidate:n?.targetCandidate||t?.targetCandidate||"REAL",virtualAnswerText:n?.virtualAnswerText||t?.virtualAnswer?.answerText||"",virtualAnswerAudioUrl:n?.virtualAnswerAudioUrl||t?.virtualAnswer?.audioUrl||"",isExtendedResponse:n?.isExtendedResponse||t?.isExtendedResponse||!1,sampleAnswers:s(),explanation:i,points:a}},getExtraProps:e=>({onUnsavedChangesChange:e.onUnsavedChangesChange,validationRef:e.validationRef}),wrapOnSave:(e,t)=>{const n=(e.sampleAnswers||[]).filter(e=>e&&""!==e.trim()),a="VIRTUAL"===e.targetCandidate;return{type:t.questionType,content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},inputType:e.inputType,audioUrl:e.audioUrl||"",imageUrl:e.imageUrl||"",questionText:"TEXT"===e.inputType?e.questionText:void 0,targetCandidate:e.targetCandidate||"REAL",virtualAnswer:a?{answerText:e.virtualAnswerText||"",audioUrl:e.virtualAnswerAudioUrl||""}:void 0,isExtendedResponse:e.isExtendedResponse||!1},points:a?0:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:a?[]:n},explanation:e?.explanation||""}},wrapOnChange:(e,t)=>{const n=e.sampleAnswers||[""],a="VIRTUAL"===e.targetCandidate;return{type:t.questionType,content:{..."object"==typeof t.state.content&&null!==t.state.content?t.state.content:{},inputType:e.inputType,audioUrl:e.audioUrl||"",imageUrl:e.imageUrl||"",questionText:"TEXT"===e.inputType?e.questionText:void 0,targetCandidate:e.targetCandidate||"REAL",virtualAnswer:a?{answerText:e.virtualAnswerText||"",audioUrl:e.virtualAnswerAudioUrl||""}:void 0,isExtendedResponse:e.isExtendedResponse||!1},points:a?0:e?.points??t.state.points??1,level:t.state.level,difficulty:t.state.difficulty,skill:t.state.skill,answer:{expectedAnswers:a?[]:n},explanation:e?.explanation||""}}};
|
|
@@ -1,48 +1 @@
|
|
|
1
|
-
export const transformGNSpeakingInterview = (
|
|
2
|
-
const contentData = question.content;
|
|
3
|
-
const answerData = question.answer;
|
|
4
|
-
const inputType = contentData?.inputType || 'TEXT';
|
|
5
|
-
const targetCandidate = contentData?.targetCandidate || 'REAL'; // VIRTUAL=Virtual, REAL=Real
|
|
6
|
-
const apiContent = {
|
|
7
|
-
inputType,
|
|
8
|
-
// Always include audioUrl — populated when AUDIO, empty string when TEXT
|
|
9
|
-
audioUrl: contentData?.audioUrl || '',
|
|
10
|
-
imageUrl: contentData?.imageUrl || '',
|
|
11
|
-
targetCandidate,
|
|
12
|
-
isExtendedResponse: contentData?.isExtendedResponse || false,
|
|
13
|
-
};
|
|
14
|
-
// Include questionText when inputType is TEXT
|
|
15
|
-
if (inputType === 'TEXT' && contentData?.questionText) {
|
|
16
|
-
apiContent.questionText = contentData.questionText;
|
|
17
|
-
}
|
|
18
|
-
// Include virtualAnswer when targetCandidate is 'VIRTUAL'
|
|
19
|
-
if (targetCandidate === 'VIRTUAL' && contentData?.virtualAnswer) {
|
|
20
|
-
apiContent.virtualAnswer = {
|
|
21
|
-
answerText: contentData.virtualAnswer.answerText || '',
|
|
22
|
-
audioUrl: contentData.virtualAnswer.audioUrl || '',
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
// Normalize: support expectedAnswers, sampleAnswers (array) and legacy sampleAnswer
|
|
26
|
-
const expectedAnswers = (() => {
|
|
27
|
-
// Virtual candidates do not have expected answers
|
|
28
|
-
if (targetCandidate === 'VIRTUAL')
|
|
29
|
-
return [];
|
|
30
|
-
const newArr = answerData?.expectedAnswers;
|
|
31
|
-
if (Array.isArray(newArr))
|
|
32
|
-
return newArr.filter((s) => s && s.trim() !== '');
|
|
33
|
-
const arr = answerData?.sampleAnswers;
|
|
34
|
-
if (Array.isArray(arr))
|
|
35
|
-
return arr.filter((s) => s && s.trim() !== '');
|
|
36
|
-
const single = answerData?.sampleAnswer;
|
|
37
|
-
if (single && single.trim() !== '')
|
|
38
|
-
return [single];
|
|
39
|
-
return [];
|
|
40
|
-
})();
|
|
41
|
-
const apiCorrectAnswer = {
|
|
42
|
-
expectedAnswers,
|
|
43
|
-
};
|
|
44
|
-
return {
|
|
45
|
-
apiContent,
|
|
46
|
-
apiCorrectAnswer,
|
|
47
|
-
};
|
|
48
|
-
};
|
|
1
|
+
export const transformGNSpeakingInterview=e=>{const r=e.content,t=e.answer,n=r?.inputType||"TEXT",s=r?.targetCandidate||"REAL",i={inputType:n,audioUrl:r?.audioUrl||"",imageUrl:r?.imageUrl||"",targetCandidate:s,isExtendedResponse:r?.isExtendedResponse||!1};"TEXT"===n&&r?.questionText&&(i.questionText=r.questionText),"VIRTUAL"===s&&r?.virtualAnswer&&(i.virtualAnswer={answerText:r.virtualAnswer.answerText||"",audioUrl:r.virtualAnswer.audioUrl||""});return{apiContent:i,apiCorrectAnswer:{expectedAnswers:(()=>{if("VIRTUAL"===s)return[];const e=t?.expectedAnswers;if(Array.isArray(e))return e.filter(e=>e&&""!==e.trim());const r=t?.sampleAnswers;if(Array.isArray(r))return r.filter(e=>e&&""!==e.trim());const n=t?.sampleAnswer;return n&&""!==n.trim()?[n]:[]})()}}};
|
|
@@ -1,16 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { ColorRegionClient } from '../../components/ColorRegionClient';
|
|
4
|
-
const imageObjectMatchingConfig = {
|
|
5
|
-
creatorTitle: 'Ghép nối hình ảnh',
|
|
6
|
-
creatorSubtitle: 'Tạo câu hỏi ghép nối hình ảnh với đối tượng',
|
|
7
|
-
instructionsLabel: 'Hướng dẫn ghép nối',
|
|
8
|
-
instructionsTips: [],
|
|
9
|
-
instructionPlaceholder: '',
|
|
10
|
-
previewTitle: 'Image Object Matching (Preview)',
|
|
11
|
-
clientTitle: 'Image Object Matching',
|
|
12
|
-
clientSubtitle: 'Ghép nối hình ảnh với đối tượng tương ứng',
|
|
13
|
-
};
|
|
14
|
-
export function ImageObjectMatchingClient(props) {
|
|
15
|
-
return _jsx(ColorRegionClient, { ...props, config: imageObjectMatchingConfig });
|
|
16
|
-
}
|
|
1
|
+
"use client";import{jsx as n}from"react/jsx-runtime";import{ColorRegionClient as t}from"../../components/ColorRegionClient";const i={creatorTitle:"Ghép nối hình ảnh",creatorSubtitle:"Tạo câu hỏi ghép nối hình ảnh với đối tượng",instructionsLabel:"Hướng dẫn ghép nối",instructionsTips:[],instructionPlaceholder:"",previewTitle:"Image Object Matching (Preview)",clientTitle:"Image Object Matching",clientSubtitle:"Ghép nối hình ảnh với đối tượng tương ứng"};export function ImageObjectMatchingClient(e){return n(t,{...e,config:i})}
|
|
@@ -1,27 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { ColorRegionCreator } from '../../components/ColorRegionCreator';
|
|
4
|
-
import { ImageObjectMatchingClient } from './ImageObjectMatchingClient';
|
|
5
|
-
const imageObjectMatchingConfig = {
|
|
6
|
-
creatorTitle: 'Ghép nối hình ảnh',
|
|
7
|
-
creatorSubtitle: 'Tạo câu hỏi ghép nối hình ảnh với đối tượng',
|
|
8
|
-
instructionsLabel: 'Hướng dẫn ghép nối',
|
|
9
|
-
instructionsTips: [
|
|
10
|
-
'Upload hình ảnh nền cần ghép nối',
|
|
11
|
-
'Thêm đối tượng kèm label (ví dụ: Tom) và tạo vùng tương ứng trên hình ảnh',
|
|
12
|
-
'Kéo thả các vùng để điều chỉnh vị trí',
|
|
13
|
-
'Thêm hướng dẫn ghép nối cho mỗi câu lệnh (ví dụ: "Match the red object with the apple")',
|
|
14
|
-
],
|
|
15
|
-
instructionPlaceholder: 'Match the red object with the apple',
|
|
16
|
-
instructionsRequired: false,
|
|
17
|
-
showColorLabel: true,
|
|
18
|
-
colorLabelPlaceholder: 'Nhập tên (ví dụ: Tom, Apple, Cat...)',
|
|
19
|
-
addItemLabel: 'Thêm đối tượng',
|
|
20
|
-
emptyItemMessage: 'Chưa có đối tượng nào. Nhấn "Thêm đối tượng" để bắt đầu.',
|
|
21
|
-
previewTitle: 'Image Object Matching (Preview)',
|
|
22
|
-
clientTitle: 'Image Object Matching',
|
|
23
|
-
clientSubtitle: 'Ghép nối hình ảnh với đối tượng tương ứng',
|
|
24
|
-
};
|
|
25
|
-
export function ImageObjectMatchingCreator(props) {
|
|
26
|
-
return (_jsx(ColorRegionCreator, { ...props, config: imageObjectMatchingConfig, ClientComponent: ImageObjectMatchingClient }));
|
|
27
|
-
}
|
|
1
|
+
"use client";import{jsx as t}from"react/jsx-runtime";import{ColorRegionCreator as n}from"../../components/ColorRegionCreator";import{ImageObjectMatchingClient as e}from"./ImageObjectMatchingClient";const i={creatorTitle:"Ghép nối hình ảnh",creatorSubtitle:"Tạo câu hỏi ghép nối hình ảnh với đối tượng",instructionsLabel:"Hướng dẫn ghép nối",instructionsTips:["Upload hình ảnh nền cần ghép nối","Thêm đối tượng kèm label (ví dụ: Tom) và tạo vùng tương ứng trên hình ảnh","Kéo thả các vùng để điều chỉnh vị trí",'Thêm hướng dẫn ghép nối cho mỗi câu lệnh (ví dụ: "Match the red object with the apple")'],instructionPlaceholder:"Match the red object with the apple",instructionsRequired:!1,showColorLabel:!0,colorLabelPlaceholder:"Nhập tên (ví dụ: Tom, Apple, Cat...)",addItemLabel:"Thêm đối tượng",emptyItemMessage:'Chưa có đối tượng nào. Nhấn "Thêm đối tượng" để bắt đầu.',previewTitle:"Image Object Matching (Preview)",clientTitle:"Image Object Matching",clientSubtitle:"Ghép nối hình ảnh với đối tượng tương ứng"};export function ImageObjectMatchingCreator(h){return t(n,{...h,config:i,ClientComponent:e})}
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export * from './ImageObjectMatchingClient';
|
|
1
|
+
export*from"./ImageObjectMatchingCreator";export*from"./ImageObjectMatchingClient";
|