@tinyweb_dev/oe-exam-sdk 1.0.6 → 1.0.7
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/components/exams/ExamCreator.js +1 -16
- package/dist/components/exams/take/ExamTakingPageContainer.js +0 -12
- package/dist/components/exams/take/components/QuestionRenderer.js +2 -15
- package/dist/components/exams/take/components/question-renderers/index.d.ts +0 -3
- package/dist/components/exams/take/components/question-renderers/index.js +0 -3
- package/dist/components/exams/take/components/speaking/renderers/SpeakingQuestionRenderer.js +0 -4
- package/dist/components/exams/take/types/speaking.types.d.ts +0 -31
- package/dist/components/exams/take/types.d.ts +0 -65
- package/dist/components/exams/take/utils/answer-transformers.js +6 -60
- package/dist/components/exams/take/utils/question-transformers.d.ts +2 -15
- package/dist/components/exams/take/utils/question-transformers.js +0 -143
- package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.d.ts +23 -0
- package/dist/components/questions/_shared/components/compact/CompactCreatorHeader.js +9 -0
- package/dist/components/questions/_shared/components/compact/CompactExampleToggle.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/CompactExampleToggle.js +8 -0
- package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.d.ts +7 -0
- package/dist/components/questions/_shared/components/compact/CompactExplanationToggle.js +14 -0
- package/dist/components/questions/_shared/components/compact/CompactFieldCard.d.ts +9 -0
- package/dist/components/questions/_shared/components/compact/CompactFieldCard.js +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.d.ts +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewBanner.js +7 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewButton.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/CompactPreviewButton.js +7 -0
- package/dist/components/questions/_shared/components/compact/index.d.ts +6 -0
- package/dist/components/questions/_shared/components/compact/index.js +6 -0
- package/dist/components/questions/_shared/config/question-types.config.js +0 -24
- package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +0 -9
- package/dist/components/questions/_shared/types/index.d.ts +0 -1
- package/dist/components/questions/_shared/types/index.js +0 -1
- package/dist/components/questions/_shared/types/look-and-choose-answer.type.d.ts +0 -13
- package/dist/components/questions/_shared/utils/answer-builder.js +0 -11
- package/dist/components/questions/components/ColorRegionCreator.js +66 -63
- package/dist/components/questions/components/QuestionSearchDropdown.js +0 -8
- package/dist/components/questions/components/QuestionTypeSelector.js +0 -2
- package/dist/components/questions/components/index.d.ts +1 -0
- package/dist/components/questions/components/index.js +1 -0
- package/dist/components/questions/creator/question-type-registry.js +0 -8
- package/dist/components/questions/groups/ArticlesGroupCard.js +2 -2
- package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -1
- package/dist/components/questions/groups/DocumentGroupCard.js +1 -1
- package/dist/components/questions/groups/ImageLabelGroupCard.js +1 -1
- package/dist/components/questions/groups/MatchAnswerGroupCard.js +2 -2
- package/dist/components/questions/question-bank/QuestionEditorRenderer.d.ts +0 -3
- package/dist/components/questions/question-bank/QuestionEditorRenderer.js +1 -10
- package/dist/components/questions/question-bank/editor-adapters/choice-adapters.d.ts +0 -2
- package/dist/components/questions/question-bank/editor-adapters/choice-adapters.js +0 -41
- package/dist/components/questions/question-bank/editor-adapters/index.d.ts +2 -2
- package/dist/components/questions/question-bank/editor-adapters/index.js +2 -2
- package/dist/components/questions/question-bank/editor-adapters/media-adapters.d.ts +0 -1
- package/dist/components/questions/question-bank/editor-adapters/media-adapters.js +0 -28
- package/dist/components/questions/question-bank/question-bank-picker.utils.js +0 -7
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +16 -16
- package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +36 -30
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupClient.js +1 -1
- package/dist/components/questions/types/answer-the-question-group/AnswerTheQuestionGroupCreator.js +7 -6
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +2 -2
- package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +11 -7
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -1
- package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +11 -5
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +5 -5
- package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +34 -28
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseAnswerGroupImageUpload.js +2 -2
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupClient.js +1 -1
- package/dist/components/questions/types/choose-the-correct-answer-group/ChooseTheCorrectAnswerGroupCreator.js +44 -28
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupClient.js +7 -7
- package/dist/components/questions/types/choose-then-answer-group/ChooseThenAnswerGroupCreator.js +8 -5
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupClient.js +2 -2
- package/dist/components/questions/types/cross-out-word-group/CrossOutWordGroupCreator.js +20 -16
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +1 -1
- package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleCreator.js +66 -29
- package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +5 -5
- package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +45 -37
- package/dist/components/questions/types/fill-in-blank-group/FillInBlankGroupCreator.js +24 -20
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +2 -2
- package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +68 -65
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +1 -1
- package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +45 -5
- package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +2 -2
- package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +18 -14
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +2 -2
- package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +30 -30
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +2 -2
- package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +24 -31
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +2 -2
- package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +29 -32
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +27 -29
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +11 -15
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +22 -24
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +17 -24
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +31 -31
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +11 -15
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -1
- package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +14 -15
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +2 -2
- package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +27 -25
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesClient.js +12 -12
- package/dist/components/questions/types/match-columns-to-make-sentences/MatchColumnsToMakeSentencesCreator.js +8 -5
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureClient.js +8 -8
- package/dist/components/questions/types/match-word-to-picture/MatchWordToPictureCreator.js +25 -20
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupClient.js +1 -1
- package/dist/components/questions/types/match-word-to-picture-group/MatchWordToPictureGroupCreator.js +13 -10
- package/dist/components/questions/types/matching-with-lines-group/MatchingWithLinesGroupCreator.js +8 -5
- package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +7 -11
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -1
- package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +15 -12
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesClient.js +1 -1
- package/dist/components/questions/types/sort-words-into-categories/SortWordsIntoCategoriesCreator.js +5 -5
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +35 -39
- package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +2 -2
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardClient.js +1 -1
- package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +7 -8
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -1
- package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +30 -23
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQAClient.js +1 -1
- package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +26 -19
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupClient.js +1 -1
- package/dist/components/questions/types/spontaneous-qa-group/SpontaneousQaGroupCreator.js +13 -11
- package/dist/components/questions/types/true-false/TrueFalseClient.js +2 -2
- package/dist/components/questions/types/true-false/TrueFalseCreator.js +35 -29
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupClient.js +1 -1
- package/dist/components/questions/types/true-false-correct-group/TrueFalseCorrectGroupCreator.js +11 -7
- package/dist/components/questions/types/true-false-group/TrueFalseGroupClient.js +1 -1
- package/dist/components/questions/types/true-false-group/TrueFalseGroupCreator.js +11 -7
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +2 -2
- package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +21 -56
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -1
- package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +30 -24
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupClient.js +3 -3
- package/dist/components/questions/types/word-order-and-match-group/WordOrderAndMatchGroupCreator.js +12 -14
- package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -1
- package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +27 -18
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupClient.js +7 -7
- package/dist/components/questions/types/word-ordering-group/WordOrderingGroupCreator.js +16 -13
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +2 -2
- package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +34 -28
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +2 -2
- package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +22 -22
- package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +21 -18
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -1
- package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +7 -4
- package/dist/components/questions/viewer/QuestionViewer.js +0 -98
- package/dist/components/results/renderers/review-question-body-movers.js +2 -26
- package/dist/components/results/review-data.js +0 -4
- package/dist/components/ui/points-input.d.ts +1 -0
- package/dist/components/ui/points-input.js +12 -2
- package/dist/shared/constants/question-skills.js +0 -11
- package/dist/shared/lib/i18n/messages/en/admin.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/en/admin.js +0 -3
- package/dist/shared/lib/i18n/messages/en.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/vi/admin.d.ts +0 -3
- package/dist/shared/lib/i18n/messages/vi/admin.js +0 -3
- package/dist/shared/lib/i18n/messages/vi.d.ts +0 -3
- package/dist/shared/lib/utils/question-reverse-transform.js +0 -19
- package/dist/shared/lib/utils/question-transform-types.d.ts +1 -23
- package/dist/shared/lib/utils/question-transform-validation.js +0 -40
- package/dist/shared/lib/utils/question-transform.js +0 -9
- package/dist/shared/types/common.types.d.ts +1 -1
- package/dist/shared/types/entities/question.types.d.ts +1 -11
- package/dist/shared/types/questions/index.d.ts +0 -3
- package/dist/shared/types/questions/index.js +0 -6
- package/package.json +1 -1
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversFillWordHintRenderer.js +0 -131
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversPictureChooseRenderer.js +0 -76
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.d.ts +0 -2
- package/dist/components/exams/take/components/question-renderers/MoversPictureFillBlankChooseRenderer.js +0 -68
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.d.ts +0 -16
- package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js +0 -212
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.d.ts +0 -32
- package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +0 -1
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.d.ts +0 -47
- package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +0 -1
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +0 -262
- package/dist/components/questions/types/gn-speaking-interview/register.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/register.js +0 -121
- package/dist/components/questions/types/gn-speaking-interview/transform.d.ts +0 -2
- package/dist/components/questions/types/gn-speaking-interview/transform.js +0 -48
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.d.ts +0 -20
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +0 -72
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.d.ts +0 -15
- package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +0 -230
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.d.ts +0 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +0 -1
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +0 -66
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +0 -42
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.d.ts +0 -6
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +0 -96
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.d.ts +0 -15
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +0 -238
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +0 -76
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +0 -19
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.d.ts +0 -7
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +0 -134
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.d.ts +0 -3
- package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +0 -617
- package/dist/components/questions/types/look-picture-fill-word-hint/index.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/index.js +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/register.js +0 -82
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.d.ts +0 -2
- package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +0 -29
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.d.ts +0 -9
- package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +0 -12
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.d.ts +0 -17
- package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +0 -47
- package/dist/shared/types/questions/look-picture-choose-correct-answer.d.ts +0 -38
- package/dist/shared/types/questions/look-picture-choose-correct-answer.js +0 -5
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.d.ts +0 -63
- package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +0 -9
- package/dist/shared/types/questions/look-picture-fill-word-hint.d.ts +0 -57
- package/dist/shared/types/questions/look-picture-fill-word-hint.js +0 -7
package/dist/components/exams/take/components/speaking/renderers/SpeakingGNInterviewRenderer.js
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
/**
|
|
4
|
-
* SpeakingGNInterviewRenderer
|
|
5
|
-
*
|
|
6
|
-
* Renderer for GN_SPEAKING_INTERVIEW (GN Speaking Part 1 & Part 2).
|
|
7
|
-
* Simulates 2 candidates: virtual (pre-recorded) + real (student records).
|
|
8
|
-
*
|
|
9
|
-
* Flow per question:
|
|
10
|
-
* - If targetCandidate === 'VIRTUAL' -> auto-play virtual answer, no mic
|
|
11
|
-
* - If targetCandidate === 'REAL' -> show mic, student records
|
|
12
|
-
*
|
|
13
|
-
* Part 2 extension: When content.imageUrl exists, displays a booklet
|
|
14
|
-
* image (collage) above the question text for Discussion with Pictures.
|
|
15
|
-
*
|
|
16
|
-
* UI: Summer Sky theme with two game-style candidate avatars at bottom,
|
|
17
|
-
* question text shown inside a teal card with hide/show question toggle badge,
|
|
18
|
-
* audio player for examiner question.
|
|
19
|
-
*/
|
|
20
|
-
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
21
|
-
import Image from 'next/image';
|
|
22
|
-
import { usePresignedFileUrl } from '../../../../../../shared/lib/hooks';
|
|
23
|
-
import { useSpeakingRendererSetup } from '../../../hooks/useSpeakingRendererSetup';
|
|
24
|
-
import { getSpeakingLayout } from '../../../../../../components/themes';
|
|
25
|
-
// Summer Sky theme assets (shared GN/KFS badges)
|
|
26
|
-
import questionBadge from '../../../../../../components/themes/summer-sky/assets/images/question-badge-sunset.png';
|
|
27
|
-
import redBadge from '../../../../../../components/themes/summer-sky/assets/images/red-badge-sunset.png';
|
|
28
|
-
import blueBadge from '../../../../../../components/themes/summer-sky/assets/images/blue-badge-sunset.png';
|
|
29
|
-
import redWrapper from '../../../../../../components/themes/summer-sky/assets/images/red-wrapper-sunset.png';
|
|
30
|
-
import blueWrapper from '../../../../../../components/themes/summer-sky/assets/images/blue-wrapper-sunset.png';
|
|
31
|
-
import bingoAvatar from '../../../../../../components/themes/summer-sky/assets/images/robot-avatar.png';
|
|
32
|
-
import userAvatar from '../../../../../../components/themes/summer-sky/assets/images/student-avatar.png';
|
|
33
|
-
import icPlay from '../../../../../../components/themes/summer-sky/assets/images/ic-play-sunset.png';
|
|
34
|
-
import icPlayViolet from '../../../../../../components/themes/summer-sky/assets/images/ic-play-violet.webp';
|
|
35
|
-
import nextSmall from '../../../../../../components/themes/summer-sky/assets/images/next-small.webp';
|
|
36
|
-
function ProgressDots({ total, currentIndex, answeredIds, questionIds, virtualIndices }) {
|
|
37
|
-
return (_jsx("div", { className: "flex items-center gap-1.5 flex-wrap justify-center", children: Array.from({ length: total }, (_, i) => {
|
|
38
|
-
const isCurrent = i === currentIndex;
|
|
39
|
-
const isAnswered = answeredIds.has(questionIds[i]);
|
|
40
|
-
const isVirtual = virtualIndices.has(i);
|
|
41
|
-
return (_jsx("div", { className: `h-2.5 w-2.5 rounded-full border-2 transition-all ${isCurrent
|
|
42
|
-
? 'scale-125 border-yellow-300 bg-yellow-400 shadow-lg'
|
|
43
|
-
: isAnswered
|
|
44
|
-
? 'border-green-400 bg-green-400'
|
|
45
|
-
: isVirtual
|
|
46
|
-
? 'border-white/30 bg-white/10'
|
|
47
|
-
: 'border-white/50 bg-white/20'}`, title: isVirtual ? 'Câu HS ảo' : `Câu ${i + 1}` }, i));
|
|
48
|
-
}) }));
|
|
49
|
-
}
|
|
50
|
-
// ============================================
|
|
51
|
-
// Main component
|
|
52
|
-
// ============================================
|
|
53
|
-
export function SpeakingGNInterviewRenderer(props) {
|
|
54
|
-
const [isQuestionVisible, setIsQuestionVisible] = useState(true);
|
|
55
|
-
const [isVirtualPlaying, setIsVirtualPlaying] = useState(false);
|
|
56
|
-
const virtualAudioRef = useRef(null);
|
|
57
|
-
// Get group payload for virtual candidate config
|
|
58
|
-
const groupPayload = props.part.groupPayload;
|
|
59
|
-
const virtualName = groupPayload?.virtualStudentName || 'Há»c sinh AI';
|
|
60
|
-
// Determine which questions are virtual
|
|
61
|
-
const virtualIndices = useMemo(() => {
|
|
62
|
-
const indices = new Set();
|
|
63
|
-
props.part.questions.forEach((q, i) => {
|
|
64
|
-
const content = q.content;
|
|
65
|
-
if (content?.targetCandidate === 'VIRTUAL') {
|
|
66
|
-
indices.add(i);
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
return indices;
|
|
70
|
-
}, [props.part.questions]);
|
|
71
|
-
// Setup with mic always enabled - we'll conditionally show/hide based on virtual/real
|
|
72
|
-
const setup = useSpeakingRendererSetup({
|
|
73
|
-
...props,
|
|
74
|
-
maxDurationMs: 60000, // 60 seconds
|
|
75
|
-
hasMic: true,
|
|
76
|
-
});
|
|
77
|
-
const Layout = getSpeakingLayout('SUMMER_SKY');
|
|
78
|
-
const content = setup.currentQuestion?.content;
|
|
79
|
-
const isVirtualTurn = content?.targetCandidate === 'VIRTUAL';
|
|
80
|
-
const isRealTurn = !isVirtualTurn;
|
|
81
|
-
// Resolve presigned URLs
|
|
82
|
-
const { previewUrl: virtualAnswerAudioUrl } = usePresignedFileUrl(content?.virtualAnswer?.audioUrl ?? '');
|
|
83
|
-
const { previewUrl: bookletImageUrl } = usePresignedFileUrl(content?.imageUrl ?? '');
|
|
84
|
-
const hasBookletImage = !!content?.imageUrl && !!bookletImageUrl;
|
|
85
|
-
// Stop virtual audio when navigating to a different question
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
if (virtualAudioRef.current) {
|
|
88
|
-
virtualAudioRef.current.pause();
|
|
89
|
-
virtualAudioRef.current.currentTime = 0;
|
|
90
|
-
}
|
|
91
|
-
setIsVirtualPlaying(false);
|
|
92
|
-
}, [setup.currentQuestion?.id]);
|
|
93
|
-
// Play virtual answer audio
|
|
94
|
-
const handlePlayVirtualAnswer = useCallback(() => {
|
|
95
|
-
if (!virtualAnswerAudioUrl)
|
|
96
|
-
return;
|
|
97
|
-
if (isVirtualPlaying && virtualAudioRef.current) {
|
|
98
|
-
virtualAudioRef.current.pause();
|
|
99
|
-
setIsVirtualPlaying(false);
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (!virtualAudioRef.current) {
|
|
103
|
-
virtualAudioRef.current = new Audio(virtualAnswerAudioUrl);
|
|
104
|
-
virtualAudioRef.current.onended = () => setIsVirtualPlaying(false);
|
|
105
|
-
virtualAudioRef.current.onerror = () => setIsVirtualPlaying(false);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
virtualAudioRef.current.src = virtualAnswerAudioUrl;
|
|
109
|
-
}
|
|
110
|
-
virtualAudioRef.current.play();
|
|
111
|
-
setIsVirtualPlaying(true);
|
|
112
|
-
}, [virtualAnswerAudioUrl, isVirtualPlaying]);
|
|
113
|
-
// Cleanup audio on unmount
|
|
114
|
-
useEffect(() => {
|
|
115
|
-
return () => {
|
|
116
|
-
if (virtualAudioRef.current) {
|
|
117
|
-
virtualAudioRef.current.pause();
|
|
118
|
-
virtualAudioRef.current = null;
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
}, []);
|
|
122
|
-
// Auto-play virtual answer after teacher audio ends (1s delay)
|
|
123
|
-
const handleTeacherAudioEnded = useCallback(() => {
|
|
124
|
-
if (!isVirtualTurn || !virtualAnswerAudioUrl)
|
|
125
|
-
return;
|
|
126
|
-
const timer = setTimeout(() => {
|
|
127
|
-
handlePlayVirtualAnswer();
|
|
128
|
-
}, 1000);
|
|
129
|
-
return () => clearTimeout(timer);
|
|
130
|
-
}, [isVirtualTurn, virtualAnswerAudioUrl, handlePlayVirtualAnswer]);
|
|
131
|
-
if (!setup.currentQuestion || !content)
|
|
132
|
-
return null;
|
|
133
|
-
// Progress tracking
|
|
134
|
-
const answeredIds = new Set(props.part.questions.map((q) => q.id).filter((id) => !!props.answers[id]));
|
|
135
|
-
const questionIds = props.part.questions.map((q) => q.id);
|
|
136
|
-
const headerRight = (_jsx(ProgressDots, { total: props.part.questions.length, currentIndex: setup.nav.currentIndex, answeredIds: answeredIds, questionIds: questionIds, virtualIndices: virtualIndices }));
|
|
137
|
-
return (_jsxs(Layout, { onExit: props.onExit, partName: props.part.name || `Part ${props.part.partNo}`, instruction: props.part.instructions || 'LISTEN AND ANSWER', audioUrl: content.audioUrl ?? '', questionNumber: setup.nav.questionNumberInPart, onPrev: setup.handlePrev, onNext: setup.handleNext, showPrev: setup.nav.showPrev, showNext: setup.nav.showNext, canGoPrev: setup.nav.canGoPrev, canGoNext: setup.nav.canGoNext, onAudioPlayingChange: setup.onAudioPlayingChange, recorder: props.isReviewMode || isVirtualTurn ? null : setup.recorder, showExistingBadge: isRealTurn && setup.showExistingBadge, teacherVideoUrl: props.teacherVideoUrl, onTeacherVideoEnded: props.onTeacherIntroPlayed, skipTeacherVideo: props.skipTeacherVideo, headerRight: headerRight, onAudioEnded: handleTeacherAudioEnded, children: [_jsxs("div", { className: "w-full h-full flex flex-col items-center relative", children: [_jsx("div", { className: "w-full flex-1", style: { minHeight: 0 }, children: _jsx("div", { className: "mx-auto", style: { width: '850px', maxWidth: '90%', height: hasBookletImage ? 'auto' : 'calc(100% - 100px)' }, children: _jsxs("div", { className: `w-full ${hasBookletImage ? '' : 'h-full'} flex flex-col items-center`, style: hasBookletImage ? {} : { paddingTop: '30px' }, children: [isQuestionVisible && content.questionText && (_jsxs("div", { className: "relative flex flex-col justify-center items-center", style: {
|
|
138
|
-
width: '882px',
|
|
139
|
-
maxWidth: '90%',
|
|
140
|
-
maxHeight: '80%',
|
|
141
|
-
paddingLeft: '77px',
|
|
142
|
-
paddingRight: '77px',
|
|
143
|
-
paddingTop: '26px',
|
|
144
|
-
paddingBottom: '26px',
|
|
145
|
-
backgroundColor: 'rgba(232, 136, 154, 0.15)',
|
|
146
|
-
borderRadius: '20px',
|
|
147
|
-
}, children: [_jsxs("div", { className: "absolute cursor-pointer", style: { top: '-19px' }, onClick: () => setIsQuestionVisible(false), children: [_jsx(Image, { src: questionBadge, alt: "", style: { width: '169px', height: '38px' } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center select-none", children: _jsx("span", { className: "font-normal text-center leading-5 text-white", style: { fontSize: '18px' }, children: "\u00E1\u00BA\u00A8n c\u00C3\u00A2u h\u00E1\u00BB\u008Fi" }) })] }), _jsx("div", { className: "max-w-full", children: _jsx("div", { className: "text-black font-bold leading-[26px] text-center max-w-full", style: { fontSize: '18px', fontFamily: '"Open Sans", sans-serif' }, children: _jsx("p", { children: content.questionText }) }) })] })), !isQuestionVisible && (_jsxs("div", { className: "cursor-pointer", onClick: () => setIsQuestionVisible(true), children: [_jsx(Image, { src: questionBadge, alt: "", style: { width: '169px', height: '38px' } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center select-none", style: { position: 'relative', top: '-30px' }, children: _jsx("span", { className: "font-normal text-center leading-5 text-white", style: { fontSize: '18px' }, children: "Hi\u00E1\u00BB\u2021n c\u00C3\u00A2u h\u00E1\u00BB\u008Fi" }) })] })), hasBookletImage && (_jsx("div", { className: "flex justify-center", style: { marginTop: '12px' }, children: _jsx("div", { className: "relative overflow-hidden", style: {
|
|
148
|
-
maxWidth: '100%',
|
|
149
|
-
borderRadius: '16px',
|
|
150
|
-
border: '3px solid rgba(230, 99, 93, 0.3)',
|
|
151
|
-
boxShadow: '0 4px 15px rgba(0,0,0,0.08)',
|
|
152
|
-
}, children: _jsx("img", { src: bookletImageUrl, alt: "Booklet", style: {
|
|
153
|
-
width: '100%',
|
|
154
|
-
height: '100%',
|
|
155
|
-
objectFit: 'cover',
|
|
156
|
-
} }) }) }))] }) }) }), _jsxs("div", { className: "flex justify-between select-none items-end", style: { width: '990px', maxWidth: '95%', paddingBottom: '40px', paddingLeft: '20px', paddingRight: '20px' }, children: [_jsxs("div", { className: `relative select-none transition-opacity duration-300 ${isVirtualTurn ? 'opacity-100' : 'opacity-50 grayscale'}`, children: [_jsx("div", { children: _jsxs("div", { className: "relative flex", children: [_jsx(Image, { src: redBadge, alt: "", style: { height: '57px', width: '258px' } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center", style: { paddingTop: '12px' }, children: _jsx("div", { className: "text-white leading-5 font-normal text-center m-0 w-full select-none", children: _jsx("p", { className: "m-0 truncate select-none", style: { fontSize: '18px', paddingLeft: '60px', paddingRight: '60px' }, children: virtualName }) }) })] }) }), _jsxs("div", { className: "absolute", style: { top: '7px', right: '-58px' }, children: [_jsx(Image, { src: redWrapper, alt: "", className: "relative", style: { width: '115px', height: '84px', zIndex: 0 } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center w-full h-full", children: _jsxs("div", { className: "relative", style: { width: '68px', height: '68px' }, children: [_jsx(Image, { src: bingoAvatar, alt: virtualName, width: 68, height: 68, className: "object-cover rounded-full select-none", style: { width: '68px', height: '68px' }, draggable: false }), _jsx("div", { className: "flex items-center justify-center absolute w-full h-full top-0 rounded-full text-white cursor-pointer", onClick: isVirtualTurn ? handlePlayVirtualAnswer : undefined, children: _jsx(Image, { src: icPlay, alt: "play", width: 16, height: 16, className: "opacity-80" }) })] }) })] }), isVirtualPlaying && (_jsx("div", { className: "absolute pointer-events-none", style: { top: '0px', right: '-130px', width: '70px', height: '84px', display: 'flex', alignItems: 'center' }, children: _jsxs("div", { className: "relative", style: { width: '70px', height: '70px' }, children: [_jsx("div", { style: {
|
|
157
|
-
position: 'absolute',
|
|
158
|
-
left: '0px',
|
|
159
|
-
top: '50%',
|
|
160
|
-
transform: 'translateY(-50%)',
|
|
161
|
-
width: '20px',
|
|
162
|
-
height: '34px',
|
|
163
|
-
borderRight: '3px solid rgba(30, 64, 175, 0.95)',
|
|
164
|
-
borderRadius: '0 50% 50% 0',
|
|
165
|
-
animation: 'soundWavePulse 1s ease-in-out infinite',
|
|
166
|
-
animationDelay: '0s',
|
|
167
|
-
} }), _jsx("div", { style: {
|
|
168
|
-
position: 'absolute',
|
|
169
|
-
left: '12px',
|
|
170
|
-
top: '50%',
|
|
171
|
-
transform: 'translateY(-50%)',
|
|
172
|
-
width: '28px',
|
|
173
|
-
height: '50px',
|
|
174
|
-
borderRight: '3px solid rgba(30, 64, 175, 0.7)',
|
|
175
|
-
borderRadius: '0 50% 50% 0',
|
|
176
|
-
animation: 'soundWavePulse 1s ease-in-out infinite',
|
|
177
|
-
animationDelay: '0.2s',
|
|
178
|
-
} }), _jsx("div", { style: {
|
|
179
|
-
position: 'absolute',
|
|
180
|
-
left: '24px',
|
|
181
|
-
top: '50%',
|
|
182
|
-
transform: 'translateY(-50%)',
|
|
183
|
-
width: '36px',
|
|
184
|
-
height: '66px',
|
|
185
|
-
borderRight: '3px solid rgba(30, 64, 175, 0.45)',
|
|
186
|
-
borderRadius: '0 50% 50% 0',
|
|
187
|
-
animation: 'soundWavePulse 1s ease-in-out infinite',
|
|
188
|
-
animationDelay: '0.4s',
|
|
189
|
-
} })] }) }))] }), isVirtualPlaying && (_jsx("div", { className: "flex-1 flex justify-center items-center text-center", style: { paddingLeft: '40px', paddingRight: '40px', paddingBottom: '40px' }, children: _jsxs("p", { className: "m-0 select-none", style: {
|
|
190
|
-
fontSize: '16px',
|
|
191
|
-
fontWeight: 700,
|
|
192
|
-
fontStyle: 'italic',
|
|
193
|
-
color: '#1a1a2e',
|
|
194
|
-
lineHeight: '1.5',
|
|
195
|
-
fontFamily: '"Open Sans", sans-serif',
|
|
196
|
-
}, children: ["Ng\u00C6\u00B0\u00E1\u00BB\u009Di ch\u00C6\u00A1i kh\u00C3\u00A1c \u00C4\u2018ang tr\u00E1\u00BA\u00A3 l\u00E1\u00BB\u009Di.", _jsx("br", {}), "Vui l\u00C3\u00B2ng nghe xong \u0111\u1EC3 ti\u1EBFp t\u1EE5c."] }) })), isVirtualTurn && !isVirtualPlaying && (_jsxs("div", { className: "flex-1 flex flex-col justify-center items-center text-center", style: { paddingLeft: '40px', paddingRight: '40px', paddingBottom: '40px' }, children: [_jsxs("p", { className: "m-0 whitespace-nowrap", style: { color: '#1a1a2e', fontSize: '16px', fontWeight: 700, fontStyle: 'italic', fontFamily: '"Open Sans", sans-serif' }, children: ["\u00E1\u00BA\u00A4n bi\u00E1\u00BB\u0192u t\u00C6\u00B0\u00E1\u00BB\u00A3ng", ' ', _jsx(Image, { src: icPlayViolet, alt: "", width: 20, height: 20, className: "inline-block" }), ' ', "tr\u00EAn avatar \u0111\u1EC3 nghe l\u1EA1i."] }), _jsxs("div", { className: "flex items-center", style: { gap: '4px' }, children: [_jsx("p", { className: "m-0", style: { color: '#1a1a2e', fontSize: '16px', fontWeight: 700, fontStyle: 'italic', fontFamily: '"Open Sans", sans-serif' }, children: "\u1EA4n n\u00FAt" }), _jsx(Image, { src: nextSmall, alt: "next", width: 34, height: 34 }), _jsx("p", { className: "m-0", style: { color: '#1a1a2e', fontSize: '16px', fontWeight: 700, fontStyle: 'italic', fontFamily: '"Open Sans", sans-serif' }, children: "\u0111\u1EC3 ti\u1EBFp t\u1EE5c." })] })] })), _jsxs("div", { className: `relative select-none transition-opacity duration-300 ${isRealTurn ? 'opacity-100' : 'opacity-50 grayscale'}`, children: [_jsx("div", { children: _jsxs("div", { className: "relative flex", children: [_jsx(Image, { src: blueBadge, alt: "", style: { height: '57px', width: '258px' } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center", style: { paddingTop: '12px' }, children: _jsx("div", { className: "text-white leading-5 font-normal text-center m-0 w-full select-none", children: _jsx("p", { className: "m-0 truncate select-none", style: { fontSize: '18px', paddingLeft: '60px', paddingRight: '60px' }, children: "B\u1EA1n" }) }) })] }) }), _jsxs("div", { className: "absolute", style: { top: '7px', left: '-58px' }, children: [_jsx(Image, { src: blueWrapper, alt: "", className: "relative", style: { width: '115px', height: '84px', zIndex: 0 } }), _jsx("div", { className: "absolute inset-0 flex justify-center items-center w-full h-full", children: _jsx("div", { className: "relative", style: { width: '68px', height: '68px' }, children: _jsx(Image, { src: userAvatar, alt: "B\u1EA1n", width: 68, height: 68, className: "object-cover rounded-full select-none", style: { width: '68px', height: '68px' }, draggable: false }) }) })] })] })] })] }), _jsx("style", { jsx: true, children: `
|
|
197
|
-
@keyframes soundWavePulse {
|
|
198
|
-
0% {
|
|
199
|
-
opacity: 0.2;
|
|
200
|
-
transform: translateY(-50%) scaleX(0.7);
|
|
201
|
-
}
|
|
202
|
-
50% {
|
|
203
|
-
opacity: 1;
|
|
204
|
-
transform: translateY(-50%) scaleX(1);
|
|
205
|
-
}
|
|
206
|
-
100% {
|
|
207
|
-
opacity: 0.2;
|
|
208
|
-
transform: translateY(-50%) scaleX(0.7);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
` })] }));
|
|
212
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { ValidationRef } from './look-and-choose-answer.type';
|
|
2
|
-
export interface GNSpeakingInterviewData {
|
|
3
|
-
/** How admin provides the question prompt */
|
|
4
|
-
inputType: 'AUDIO' | 'TEXT';
|
|
5
|
-
/** Audio file URL — required when inputType is 'AUDIO' */
|
|
6
|
-
audioUrl?: string;
|
|
7
|
-
/** Question text — required when inputType is 'TEXT', backend converts via TTS */
|
|
8
|
-
questionText?: string;
|
|
9
|
-
/** Which candidate this question targets */
|
|
10
|
-
targetCandidate: 'A' | 'B';
|
|
11
|
-
/** Virtual student's pre-recorded answer text */
|
|
12
|
-
virtualAnswerText?: string;
|
|
13
|
-
/** Virtual student's pre-recorded answer audio URL */
|
|
14
|
-
virtualAnswerAudioUrl?: string;
|
|
15
|
-
/** Whether this is an Extended Response question */
|
|
16
|
-
isExtendedResponse?: boolean;
|
|
17
|
-
/** Optional sample answers for AI grading reference (for real candidate questions) */
|
|
18
|
-
sampleAnswers?: string[];
|
|
19
|
-
/** Explanation for review */
|
|
20
|
-
explanation?: string;
|
|
21
|
-
/** Points per question (0 for virtual) */
|
|
22
|
-
points?: number;
|
|
23
|
-
}
|
|
24
|
-
export interface GNSpeakingInterviewCreatorProps {
|
|
25
|
-
initialData?: GNSpeakingInterviewData;
|
|
26
|
-
onSave?: (data: GNSpeakingInterviewData) => void;
|
|
27
|
-
onCancel?: () => void;
|
|
28
|
-
onChange?: (data: GNSpeakingInterviewData) => void;
|
|
29
|
-
externalErrors?: string[];
|
|
30
|
-
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
|
|
31
|
-
validationRef?: React.MutableRefObject<ValidationRef | null>;
|
|
32
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { ValidationRef } from './look-and-choose-answer.type';
|
|
2
|
-
export type { ValidationRef };
|
|
3
|
-
export interface LookPictureFillWordHintSubQuestion {
|
|
4
|
-
id: string;
|
|
5
|
-
imageUrl: string;
|
|
6
|
-
word: string;
|
|
7
|
-
correctAnswer: string;
|
|
8
|
-
points?: number;
|
|
9
|
-
groupNumber?: number;
|
|
10
|
-
}
|
|
11
|
-
export interface LookPictureFillWordHintGroup {
|
|
12
|
-
number: number;
|
|
13
|
-
questionCount: number;
|
|
14
|
-
}
|
|
15
|
-
export interface LookPictureFillWordHintData {
|
|
16
|
-
subQuestions: LookPictureFillWordHintSubQuestion[];
|
|
17
|
-
hintLetters?: string[];
|
|
18
|
-
hintLettersByGroup?: Record<number, string[]>;
|
|
19
|
-
caseSensitive?: boolean;
|
|
20
|
-
groups?: LookPictureFillWordHintGroup[];
|
|
21
|
-
}
|
|
22
|
-
export interface LookPictureFillWordHintCreatorProps {
|
|
23
|
-
initialData?: LookPictureFillWordHintData;
|
|
24
|
-
onSave?: (data: LookPictureFillWordHintData) => void;
|
|
25
|
-
onSaveToQuestionBank?: (data: LookPictureFillWordHintData) => void;
|
|
26
|
-
onCancel?: () => void;
|
|
27
|
-
onChange?: (data: LookPictureFillWordHintData) => void;
|
|
28
|
-
onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void;
|
|
29
|
-
externalErrors?: string[];
|
|
30
|
-
groups?: LookPictureFillWordHintGroup[];
|
|
31
|
-
questionIndexInPart?: number;
|
|
32
|
-
partId?: string;
|
|
33
|
-
validationRef?: React.MutableRefObject<ValidationRef | null>;
|
|
34
|
-
}
|
|
35
|
-
export interface LookPictureFillWordHintQuestionData {
|
|
36
|
-
subQuestions: LookPictureFillWordHintSubQuestion[];
|
|
37
|
-
hintLetters?: string[];
|
|
38
|
-
hintLettersByGroup?: Record<number, string[]>;
|
|
39
|
-
caseSensitive?: boolean;
|
|
40
|
-
groups?: LookPictureFillWordHintGroup[];
|
|
41
|
-
}
|
|
42
|
-
export interface LookPictureFillWordHintClientProps {
|
|
43
|
-
questionData: LookPictureFillWordHintQuestionData;
|
|
44
|
-
onSubmit?: (answers: Record<string, string>) => void;
|
|
45
|
-
isReviewMode?: boolean;
|
|
46
|
-
userAnswer?: Record<string, string>;
|
|
47
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.d.ts
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import type { GNSpeakingInterviewCreatorProps } from '../../_shared/types/gn-speaking-interview.type';
|
|
2
|
-
export declare function GNSpeakingInterviewCreator({ initialData, onSave, onChange, externalErrors, validationRef, }: GNSpeakingInterviewCreatorProps): import("react").JSX.Element;
|
|
@@ -1,262 +0,0 @@
|
|
|
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
|
-
}
|