@tinyweb_dev/oe-exam-sdk 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (514) hide show
  1. package/dist/api/exam-create.js +1 -179
  2. package/dist/api/exam-create.types.js +1 -1
  3. package/dist/api/exam-questions.d.ts +1 -1
  4. package/dist/api/exam-questions.js +1 -98
  5. package/dist/api/exam-questions.types.d.ts +45 -1
  6. package/dist/api/exam-questions.types.js +1 -1
  7. package/dist/api/exam-taking.js +1 -73
  8. package/dist/api/exam-taking.types.js +1 -1
  9. package/dist/api/index.js +1 -6
  10. package/dist/api/result-review.js +1 -50
  11. package/dist/api/result-review.types.js +1 -1
  12. package/dist/components/CueCardEditor.js +1 -58
  13. package/dist/components/common/ResolvedImage.js +1 -15
  14. package/dist/components/exams/CompletionDialog.d.ts +8 -0
  15. package/dist/components/exams/CompletionDialog.js +1 -0
  16. package/dist/components/exams/CreateExamInfoForm.d.ts +4 -1
  17. package/dist/components/exams/CreateExamInfoForm.js +1 -35
  18. package/dist/components/exams/CreateExamPageContainer.d.ts +3 -0
  19. package/dist/components/exams/CreateExamPageContainer.js +1 -103
  20. package/dist/components/exams/ExamCreator.d.ts +9 -3
  21. package/dist/components/exams/ExamCreator.js +1 -89
  22. package/dist/components/exams/ExamPartTabs.d.ts +1 -1
  23. package/dist/components/exams/ExamPartTabs.js +1 -40
  24. package/dist/components/exams/ExamPreviewDialog.d.ts +20 -0
  25. package/dist/components/exams/ExamPreviewDialog.js +1 -0
  26. package/dist/components/exams/ExamQuestionGrid.d.ts +1 -1
  27. package/dist/components/exams/ExamQuestionGrid.js +1 -18
  28. package/dist/components/exams/ExamQuestionsPageContainer.d.ts +3 -0
  29. package/dist/components/exams/ExamQuestionsPageContainer.js +1 -90
  30. package/dist/components/exams/ExamViewDialog.d.ts +24 -0
  31. package/dist/components/exams/ExamViewDialog.js +1 -0
  32. package/dist/components/exams/PartAudioUploader.js +1 -9
  33. package/dist/components/exams/TemplateSelector.js +1 -66
  34. package/dist/components/exams/UnsavedChangesDialog.d.ts +8 -0
  35. package/dist/components/exams/UnsavedChangesDialog.js +1 -0
  36. package/dist/components/exams/api.d.ts +1 -1
  37. package/dist/components/exams/api.js +1 -3
  38. package/dist/components/exams/create-exam.texts.d.ts +58 -0
  39. package/dist/components/exams/create-exam.texts.js +1 -0
  40. package/dist/components/exams/create-exam.validation.js +1 -18
  41. package/dist/components/exams/exam-question-status.d.ts +9 -0
  42. package/dist/components/exams/exam-question-status.js +1 -0
  43. package/dist/components/exams/exam-template.utils.js +1 -18
  44. package/dist/components/exams/index.d.ts +11 -1
  45. package/dist/components/exams/index.js +1 -10
  46. package/dist/components/exams/take/ExamTakingPageContainer.js +1 -153
  47. package/dist/components/exams/take/GenericQuestionRenderer.js +1 -14
  48. package/dist/components/exams/take/exam-taking.utils.js +1 -88
  49. package/dist/components/exams/take/hooks/useSpeakingRecorder.js +1 -24
  50. package/dist/components/exams/take/index.js +1 -2
  51. package/dist/components/exams/take/types.js +1 -1
  52. package/dist/components/index.js +1 -42
  53. package/dist/components/questions/_shared/config/question-types.config.js +1 -333
  54. package/dist/components/questions/_shared/constants/color-palette.js +1 -21
  55. package/dist/components/questions/_shared/hooks/useAnswerGroupQuestionGroup.js +1 -105
  56. package/dist/components/questions/_shared/hooks/useBasicQuestionGroup.js +1 -222
  57. package/dist/components/questions/_shared/hooks/useCompareImagesGroup.js +1 -140
  58. package/dist/components/questions/_shared/hooks/useImageQuestionGroup.js +1 -123
  59. package/dist/components/questions/_shared/hooks/useInfoExchangeGroup.js +1 -50
  60. package/dist/components/questions/_shared/hooks/useQuestionFormState.js +1 -54
  61. package/dist/components/questions/_shared/hooks/useQuestionInitialization.js +1 -123
  62. package/dist/components/questions/_shared/hooks/useQuestionListGroup.js +1 -121
  63. package/dist/components/questions/_shared/hooks/useQuestionOnChange.js +1 -70
  64. package/dist/components/questions/_shared/hooks/useSceneQuestionGroup.js +1 -194
  65. package/dist/components/questions/_shared/index.js +1 -7
  66. package/dist/components/questions/_shared/types/answer-the-question.type.js +1 -1
  67. package/dist/components/questions/_shared/types/arrange-letters-into-words.type.js +1 -1
  68. package/dist/components/questions/_shared/types/choose-correct-adjective.type.js +1 -3
  69. package/dist/components/questions/_shared/types/color-region.config.js +1 -1
  70. package/dist/components/questions/_shared/types/fill-in-blank.type.js +1 -1
  71. package/dist/components/questions/_shared/types/fill-missing-words-in-grid.type.js +1 -1
  72. package/dist/components/questions/_shared/types/gn-speaking-interview.type.js +1 -1
  73. package/dist/components/questions/_shared/types/index.js +1 -12
  74. package/dist/components/questions/_shared/types/label-the-picture.type.js +1 -1
  75. package/dist/components/questions/_shared/types/listen-and-choose-from-answer-group.type.js +1 -1
  76. package/dist/components/questions/_shared/types/listen-and-choose-objects-in-scene.type.js +1 -1
  77. package/dist/components/questions/_shared/types/listen-and-drag-objects-into-scene.type.js +1 -1
  78. package/dist/components/questions/_shared/types/listen-and-speak-answer.type.js +1 -1
  79. package/dist/components/questions/_shared/types/listen-and-speak-compare-images.type.js +1 -1
  80. package/dist/components/questions/_shared/types/listen-and-speak-image-group.type.js +1 -1
  81. package/dist/components/questions/_shared/types/listen-and-speak-info-exchange.type.js +1 -1
  82. package/dist/components/questions/_shared/types/listen-and-speak-odd-one-out.type.js +1 -3
  83. package/dist/components/questions/_shared/types/listen-and-speak-question-list.type.js +1 -1
  84. package/dist/components/questions/_shared/types/listen-and-speak-with-story-images.type.js +1 -3
  85. package/dist/components/questions/_shared/types/listen-and-tick-answer.type.js +1 -1
  86. package/dist/components/questions/_shared/types/listening-drawline.type.js +1 -1
  87. package/dist/components/questions/_shared/types/look-and-choose-answer.type.js +1 -1
  88. package/dist/components/questions/_shared/types/look-picture-fill-word-hint.type.js +1 -1
  89. package/dist/components/questions/_shared/types/match-by-writing-answer.type.js +1 -1
  90. package/dist/components/questions/_shared/types/multiple-choice.type.js +1 -1
  91. package/dist/components/questions/_shared/types/question-group.type.js +1 -11
  92. package/dist/components/questions/_shared/types/read-and-color-objects.type.js +1 -1
  93. package/dist/components/questions/_shared/types/read-displayed-content.type.js +1 -1
  94. package/dist/components/questions/_shared/types/read-passage-and-answer-questions.type.js +1 -1
  95. package/dist/components/questions/_shared/types/speaking-describe-image.type.js +1 -1
  96. package/dist/components/questions/_shared/types/spontaneous-qa.type.js +1 -1
  97. package/dist/components/questions/_shared/types/word-fill-paragraph-with-options.type.js +1 -1
  98. package/dist/components/questions/_shared/types/word-fill-paragraph.type.js +1 -1
  99. package/dist/components/questions/_shared/types/word-fill-structured-form.type.js +1 -1
  100. package/dist/components/questions/_shared/types/word-ordering.type.js +1 -1
  101. package/dist/components/questions/_shared/types/write-a-short-letter.type.js +1 -1
  102. package/dist/components/questions/_shared/types/write-correct-verb-form.type.js +1 -3
  103. package/dist/components/questions/_shared/types/write-sentences.type.js +1 -1
  104. package/dist/components/questions/_shared/types/write-short-paragraph.type.js +1 -1
  105. package/dist/components/questions/_shared/utils/answer-builder.js +1 -79
  106. package/dist/components/questions/_shared/utils/createColorRegionRegistration.js +1 -39
  107. package/dist/components/questions/_shared/utils/id-generator.js +1 -3
  108. package/dist/components/questions/_shared/utils/question-validation.js +1 -21
  109. package/dist/components/questions/components/ColorNode.js +1 -85
  110. package/dist/components/questions/components/ColorRegionClient.js +1 -545
  111. package/dist/components/questions/components/ColorRegionCreator.js +1 -420
  112. package/dist/components/questions/components/ColorRegionNode.js +1 -59
  113. package/dist/components/questions/components/CreatorGuide.js +1 -103
  114. package/dist/components/questions/components/NameNode.js +1 -26
  115. package/dist/components/questions/components/QuestionBankOption.js +1 -46
  116. package/dist/components/questions/components/QuestionBasicInfoForm.js +1 -9
  117. package/dist/components/questions/components/QuestionCommonFields.js +1 -33
  118. package/dist/components/questions/components/QuestionSearchDropdown.js +1 -258
  119. package/dist/components/questions/components/QuestionTypeSelector.js +1 -32
  120. package/dist/components/questions/components/QuestionTypeSpecificForms.js +1 -6
  121. package/dist/components/questions/components/RegionNode.js +1 -94
  122. package/dist/components/questions/components/index.js +1 -14
  123. package/dist/components/questions/creator/QuestionCreator.js +1 -90
  124. package/dist/components/questions/creator/QuestionGroupCreator.js +1 -119
  125. package/dist/components/questions/creator/dedicated-component-router.js +1 -79
  126. package/dist/components/questions/creator/index.js +1 -3
  127. package/dist/components/questions/creator/question-type-registry.js +1 -80
  128. package/dist/components/questions/groups/ArticlesGroupCard.js +1 -121
  129. package/dist/components/questions/groups/BasicQuestionGroupCard.js +1 -50
  130. package/dist/components/questions/groups/DocumentGroupCard.js +1 -114
  131. package/dist/components/questions/groups/MatchAnswerGroupCard.js +1 -25
  132. package/dist/components/questions/index.js +1 -10
  133. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionClient.js +1 -37
  134. package/dist/components/questions/types/answer-the-question/AnswerTheQuestionCreator.js +1 -351
  135. package/dist/components/questions/types/answer-the-question/register.js +1 -67
  136. package/dist/components/questions/types/answer-the-question/transform.js +1 -36
  137. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsClient.js +1 -141
  138. package/dist/components/questions/types/arrange-letters-into-words/ArrangeLettersIntoWordsCreator.js +1 -118
  139. package/dist/components/questions/types/arrange-letters-into-words/index.js +1 -2
  140. package/dist/components/questions/types/arrange-letters-into-words/register.js +1 -4
  141. package/dist/components/questions/types/arrange-letters-into-words/transform.js +1 -24
  142. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveClient.js +1 -64
  143. package/dist/components/questions/types/choose-correct-adjective/ChooseCorrectAdjectiveCreator.js +1 -169
  144. package/dist/components/questions/types/choose-correct-adjective/index.js +1 -2
  145. package/dist/components/questions/types/choose-correct-adjective/register.js +1 -55
  146. package/dist/components/questions/types/choose-correct-adjective/transform.js +1 -16
  147. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerClient.js +1 -131
  148. package/dist/components/questions/types/choose-the-correct-answer/ChooseTheCorrectAnswerCreator.js +1 -581
  149. package/dist/components/questions/types/choose-the-correct-answer/index.js +1 -2
  150. package/dist/components/questions/types/choose-the-correct-answer/register.js +1 -101
  151. package/dist/components/questions/types/choose-the-correct-answer/transform.js +1 -43
  152. package/dist/components/questions/types/fill-in-blank/FillInBlankClient.js +1 -275
  153. package/dist/components/questions/types/fill-in-blank/FillInBlankCreator.js +1 -448
  154. package/dist/components/questions/types/fill-in-blank/index.js +1 -2
  155. package/dist/components/questions/types/fill-in-blank/register.js +1 -138
  156. package/dist/components/questions/types/fill-in-blank/transform.js +1 -59
  157. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridClient.js +1 -117
  158. package/dist/components/questions/types/fill-missing-words-in-grid/FillMissingWordsInGridCreator.js +1 -671
  159. package/dist/components/questions/types/fill-missing-words-in-grid/index.js +1 -2
  160. package/dist/components/questions/types/fill-missing-words-in-grid/register.js +1 -37
  161. package/dist/components/questions/types/fill-missing-words-in-grid/transform.js +1 -15
  162. package/dist/components/questions/types/gn-speaking-interview/GNSpeakingInterviewCreator.js +1 -262
  163. package/dist/components/questions/types/gn-speaking-interview/register.js +1 -121
  164. package/dist/components/questions/types/gn-speaking-interview/transform.js +1 -48
  165. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingClient.js +1 -16
  166. package/dist/components/questions/types/image-object-matching/ImageObjectMatchingCreator.js +1 -27
  167. package/dist/components/questions/types/image-object-matching/index.js +1 -2
  168. package/dist/components/questions/types/image-object-matching/register.js +1 -3
  169. package/dist/components/questions/types/image-object-matching/transform.js +1 -2
  170. package/dist/components/questions/types/label-the-picture/LabelThePictureClient.js +1 -102
  171. package/dist/components/questions/types/label-the-picture/LabelThePictureCreator.js +1 -344
  172. package/dist/components/questions/types/label-the-picture/index.js +1 -2
  173. package/dist/components/questions/types/label-the-picture/register.js +1 -54
  174. package/dist/components/questions/types/label-the-picture/transform.js +1 -28
  175. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupClient.js +1 -78
  176. package/dist/components/questions/types/listen-and-choose-from-answer-group/ListenAndChooseFromAnswerGroupCreator.js +1 -232
  177. package/dist/components/questions/types/listen-and-choose-from-answer-group/index.js +1 -2
  178. package/dist/components/questions/types/listen-and-choose-from-answer-group/register.js +1 -82
  179. package/dist/components/questions/types/listen-and-choose-from-answer-group/transform.js +1 -27
  180. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneClient.js +1 -70
  181. package/dist/components/questions/types/listen-and-choose-objects-in-scene/ListenAndChooseObjectsInSceneCreator.js +1 -386
  182. package/dist/components/questions/types/listen-and-choose-objects-in-scene/index.js +1 -2
  183. package/dist/components/questions/types/listen-and-choose-objects-in-scene/register.js +1 -100
  184. package/dist/components/questions/types/listen-and-choose-objects-in-scene/transform.js +1 -33
  185. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneClient.js +1 -106
  186. package/dist/components/questions/types/listen-and-drag-objects-into-scene/ListenAndDragObjectsIntoSceneCreator.js +1 -457
  187. package/dist/components/questions/types/listen-and-drag-objects-into-scene/index.js +1 -2
  188. package/dist/components/questions/types/listen-and-drag-objects-into-scene/register.js +1 -110
  189. package/dist/components/questions/types/listen-and-drag-objects-into-scene/transform.js +1 -35
  190. package/dist/components/questions/types/listen-and-speak-answer/ArrowIndicator.js +1 -8
  191. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerClient.js +1 -22
  192. package/dist/components/questions/types/listen-and-speak-answer/ListenAndSpeakAnswerCreator.js +1 -291
  193. package/dist/components/questions/types/listen-and-speak-answer/index.js +1 -2
  194. package/dist/components/questions/types/listen-and-speak-answer/register.js +1 -84
  195. package/dist/components/questions/types/listen-and-speak-answer/transform.js +1 -27
  196. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesClient.js +1 -32
  197. package/dist/components/questions/types/listen-and-speak-compare-images/ListenAndSpeakCompareImagesCreator.js +1 -295
  198. package/dist/components/questions/types/listen-and-speak-compare-images/register.js +1 -79
  199. package/dist/components/questions/types/listen-and-speak-compare-images/transform.js +1 -27
  200. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupClient.js +1 -34
  201. package/dist/components/questions/types/listen-and-speak-image-group/ListenAndSpeakImageGroupCreator.js +1 -285
  202. package/dist/components/questions/types/listen-and-speak-image-group/register.js +1 -81
  203. package/dist/components/questions/types/listen-and-speak-image-group/transform.js +1 -26
  204. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeClient.js +1 -38
  205. package/dist/components/questions/types/listen-and-speak-info-exchange/ListenAndSpeakInfoExchangeCreator.js +1 -385
  206. package/dist/components/questions/types/listen-and-speak-info-exchange/register.js +1 -87
  207. package/dist/components/questions/types/listen-and-speak-info-exchange/transform.js +1 -32
  208. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutClient.js +1 -26
  209. package/dist/components/questions/types/listen-and-speak-odd-one-out/ListenAndSpeakOddOneOutCreator.js +1 -247
  210. package/dist/components/questions/types/listen-and-speak-odd-one-out/register.js +1 -59
  211. package/dist/components/questions/types/listen-and-speak-odd-one-out/transform.js +1 -18
  212. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListClient.js +1 -26
  213. package/dist/components/questions/types/listen-and-speak-question-list/ListenAndSpeakQuestionListCreator.js +1 -256
  214. package/dist/components/questions/types/listen-and-speak-question-list/register.js +1 -81
  215. package/dist/components/questions/types/listen-and-speak-question-list/transform.js +1 -26
  216. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesClient.js +1 -26
  217. package/dist/components/questions/types/listen-and-speak-with-story-images/ListenAndSpeakWithStoryImagesCreator.js +1 -230
  218. package/dist/components/questions/types/listen-and-speak-with-story-images/register.js +1 -56
  219. package/dist/components/questions/types/listen-and-speak-with-story-images/transform.js +1 -16
  220. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerClient.js +1 -72
  221. package/dist/components/questions/types/look-picture-choose-correct-answer/LookPictureChooseCorrectAnswerCreator.js +1 -230
  222. package/dist/components/questions/types/look-picture-choose-correct-answer/index.js +1 -1
  223. package/dist/components/questions/types/look-picture-choose-correct-answer/register.js +1 -66
  224. package/dist/components/questions/types/look-picture-choose-correct-answer/transform.js +1 -42
  225. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerClient.js +1 -96
  226. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/LookPictureFillBlankChooseAnswerCreator.js +1 -238
  227. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/index.js +1 -2
  228. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/register.js +1 -76
  229. package/dist/components/questions/types/look-picture-fill-blank-choose-answer/transform.js +1 -19
  230. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintClient.js +1 -134
  231. package/dist/components/questions/types/look-picture-fill-word-hint/LookPictureFillWordHintCreator.js +1 -617
  232. package/dist/components/questions/types/look-picture-fill-word-hint/index.js +1 -2
  233. package/dist/components/questions/types/look-picture-fill-word-hint/register.js +1 -82
  234. package/dist/components/questions/types/look-picture-fill-word-hint/transform.js +1 -29
  235. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerClient.js +1 -38
  236. package/dist/components/questions/types/match-by-writing-answer/MatchByWritingAnswerCreator.js +1 -359
  237. package/dist/components/questions/types/match-by-writing-answer/index.js +1 -3
  238. package/dist/components/questions/types/match-by-writing-answer/register.js +1 -58
  239. package/dist/components/questions/types/match-by-writing-answer/transform.js +1 -61
  240. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsClient.js +1 -16
  241. package/dist/components/questions/types/read-and-color-objects/ReadAndColorObjectsCreator.js +1 -23
  242. package/dist/components/questions/types/read-and-color-objects/index.js +1 -2
  243. package/dist/components/questions/types/read-and-color-objects/register.js +1 -3
  244. package/dist/components/questions/types/read-and-color-objects/transform.js +1 -36
  245. package/dist/components/questions/types/read-displayed-content/ReadDisplayedContentCreator.js +1 -125
  246. package/dist/components/questions/types/read-displayed-content/register.js +1 -64
  247. package/dist/components/questions/types/read-displayed-content/transform.js +1 -12
  248. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsClient.js +1 -64
  249. package/dist/components/questions/types/read-passage-and-answer-questions/ReadPassageAndAnswerQuestionsCreator.js +1 -511
  250. package/dist/components/questions/types/read-passage-and-answer-questions/index.js +1 -2
  251. package/dist/components/questions/types/read-passage-and-answer-questions/register.js +1 -58
  252. package/dist/components/questions/types/read-passage-and-answer-questions/transform.js +1 -28
  253. package/dist/components/questions/types/speaking-conversation/SpeakingConversationCreator.js +1 -373
  254. package/dist/components/questions/types/speaking-conversation/SpeakingConversationPreviewClient.js +1 -41
  255. package/dist/components/questions/types/speaking-conversation/register.js +1 -110
  256. package/dist/components/questions/types/speaking-conversation/transform.js +1 -34
  257. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -74
  258. package/dist/components/questions/types/speaking-cue-card/register.js +1 -52
  259. package/dist/components/questions/types/speaking-cue-card/transform.js +1 -12
  260. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageClient.js +1 -10
  261. package/dist/components/questions/types/speaking-describe-image/SpeakingDescribeImageCreator.js +1 -161
  262. package/dist/components/questions/types/speaking-describe-image/index.js +1 -2
  263. package/dist/components/questions/types/speaking-describe-image/register.js +1 -69
  264. package/dist/components/questions/types/speaking-describe-image/transform.js +1 -16
  265. package/dist/components/questions/types/spontaneous-qa/SpontaneousQACreator.js +1 -234
  266. package/dist/components/questions/types/spontaneous-qa/register.js +1 -93
  267. package/dist/components/questions/types/spontaneous-qa/transform.js +1 -34
  268. package/dist/components/questions/types/true-false/TrueFalseClient.js +1 -75
  269. package/dist/components/questions/types/true-false/TrueFalseCreator.js +1 -244
  270. package/dist/components/questions/types/true-false/index.js +1 -2
  271. package/dist/components/questions/types/true-false/register.js +1 -77
  272. package/dist/components/questions/types/true-false/transform.js +1 -32
  273. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphClient.js +1 -233
  274. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphCreator.js +1 -631
  275. package/dist/components/questions/types/word-fill-paragraph/WordFillParagraphWrapper.js +1 -28
  276. package/dist/components/questions/types/word-fill-paragraph/index.js +1 -2
  277. package/dist/components/questions/types/word-fill-paragraph/register.js +1 -136
  278. package/dist/components/questions/types/word-fill-paragraph/transform.js +1 -56
  279. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormClient.js +1 -138
  280. package/dist/components/questions/types/word-fill-structured-form/WordFillStructuredFormCreator.js +1 -261
  281. package/dist/components/questions/types/word-fill-structured-form/index.js +1 -2
  282. package/dist/components/questions/types/word-fill-structured-form/register.js +1 -67
  283. package/dist/components/questions/types/word-fill-structured-form/transform.js +1 -40
  284. package/dist/components/questions/types/word-ordering/WordOrderingClient.js +1 -84
  285. package/dist/components/questions/types/word-ordering/WordOrderingCreator.js +1 -192
  286. package/dist/components/questions/types/word-ordering/index.js +1 -2
  287. package/dist/components/questions/types/word-ordering/register.js +1 -46
  288. package/dist/components/questions/types/word-ordering/transform.js +1 -24
  289. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterClient.js +1 -98
  290. package/dist/components/questions/types/write-a-short-letter/WriteAShortLetterCreator.js +1 -320
  291. package/dist/components/questions/types/write-a-short-letter/index.js +1 -2
  292. package/dist/components/questions/types/write-a-short-letter/register.js +1 -68
  293. package/dist/components/questions/types/write-a-short-letter/transform.js +1 -25
  294. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormClient.js +1 -61
  295. package/dist/components/questions/types/write-correct-verb-form/WriteCorrectVerbFormCreator.js +1 -196
  296. package/dist/components/questions/types/write-correct-verb-form/index.js +1 -2
  297. package/dist/components/questions/types/write-correct-verb-form/register.js +1 -62
  298. package/dist/components/questions/types/write-correct-verb-form/transform.js +1 -17
  299. package/dist/components/questions/types/write-sentences/WriteSentencesClient.js +1 -22
  300. package/dist/components/questions/types/write-sentences/WriteSentencesCreator.js +1 -174
  301. package/dist/components/questions/types/write-sentences/index.js +1 -2
  302. package/dist/components/questions/types/write-sentences/register.js +1 -58
  303. package/dist/components/questions/types/write-sentences/transform.js +1 -28
  304. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphClient.js +1 -96
  305. package/dist/components/questions/types/write-short-paragraph/WriteShortParagraphCreator.js +1 -161
  306. package/dist/components/questions/types/write-short-paragraph/index.js +1 -2
  307. package/dist/components/questions/types/write-short-paragraph/register.js +1 -4
  308. package/dist/components/questions/types/write-short-paragraph/transform.js +1 -21
  309. package/dist/components/questions/viewer/QuestionGroupViewer.js +1 -36
  310. package/dist/components/questions/viewer/QuestionViewer.js +1 -446
  311. package/dist/components/questions/viewer/index.js +1 -2
  312. package/dist/components/results/ResultReviewPageContainer.js +1 -98
  313. package/dist/components/results/ReviewQuestionRenderer.js +1 -72
  314. package/dist/components/results/index.js +1 -2
  315. package/dist/components/results/result-review.utils.js +1 -6
  316. package/dist/components/results/review-data.js +1 -150
  317. package/dist/components/results/review-renderer.utils.js +1 -97
  318. package/dist/components/results/review-types.js +1 -1
  319. package/dist/components/themes/cambridge-yle/CambridgeYleAudioPlayer.js +1 -138
  320. package/dist/components/themes/cambridge-yle/CambridgeYleBottomNav.js +1 -12
  321. package/dist/components/themes/cambridge-yle/CambridgeYleExamLayout.js +1 -18
  322. package/dist/components/themes/cambridge-yle/CambridgeYleExampleText.js +1 -11
  323. package/dist/components/themes/cambridge-yle/CambridgeYleHeader.js +1 -11
  324. package/dist/components/themes/cambridge-yle/CambridgeYleInstructionBanner.js +1 -9
  325. package/dist/components/themes/cambridge-yle/CambridgeYleNavButton.js +1 -13
  326. package/dist/components/themes/cambridge-yle/CambridgeYlePartBanner.js +1 -8
  327. package/dist/components/themes/cambridge-yle/CambridgeYleProgressBar.js +1 -6
  328. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCard.js +1 -5
  329. package/dist/components/themes/cambridge-yle/CambridgeYleQuestionCardFullWidth.js +1 -9
  330. package/dist/components/themes/cambridge-yle/CambridgeYleSidebar.js +1 -41
  331. package/dist/components/themes/cambridge-yle/CambridgeYleTimer.js +1 -25
  332. package/dist/components/themes/cambridge-yle/index.js +1 -13
  333. package/dist/components/themes/english-certification/EcAnswerTheQuestion.js +1 -24
  334. package/dist/components/themes/english-certification/EcMatchByWritingAnswer.js +1 -88
  335. package/dist/components/themes/english-certification/EcWordFillParagraph.js +1 -55
  336. package/dist/components/themes/english-certification/EcWordFillStructuredForm.js +1 -156
  337. package/dist/components/themes/english-certification/EcWritingTask.js +1 -34
  338. package/dist/components/themes/english-certification/EnglishCertificationAudioPlayer.js +1 -114
  339. package/dist/components/themes/english-certification/EnglishCertificationBrand.js +1 -11
  340. package/dist/components/themes/english-certification/EnglishCertificationExamLayout.js +1 -21
  341. package/dist/components/themes/english-certification/EnglishCertificationFlagButton.js +1 -15
  342. package/dist/components/themes/english-certification/EnglishCertificationFooterBar.js +1 -19
  343. package/dist/components/themes/english-certification/EnglishCertificationGroupedQuestion.js +1 -148
  344. package/dist/components/themes/english-certification/EnglishCertificationHeader.js +1 -31
  345. package/dist/components/themes/english-certification/EnglishCertificationNavigationPanel.js +1 -71
  346. package/dist/components/themes/english-certification/EnglishCertificationOptionSelector.js +1 -33
  347. package/dist/components/themes/english-certification/EnglishCertificationPassagePanel.js +1 -16
  348. package/dist/components/themes/english-certification/EnglishCertificationQuestionCard.js +1 -22
  349. package/dist/components/themes/english-certification/EnglishCertificationQuestionItem.js +1 -37
  350. package/dist/components/themes/english-certification/EnglishCertificationQuestionRenderer.js +1 -72
  351. package/dist/components/themes/english-certification/EnglishCertificationReadingSection.js +1 -90
  352. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestion.js +1 -18
  353. package/dist/components/themes/english-certification/EnglishCertificationSingleQuestionList.js +1 -29
  354. package/dist/components/themes/english-certification/english-certification-theme.js +1 -58
  355. package/dist/components/themes/english-certification/grouped-question-utils.js +1 -218
  356. package/dist/components/themes/english-certification/index.js +1 -21
  357. package/dist/components/themes/index.js +1 -6
  358. package/dist/components/themes/summer-sky/SummerSkyAudioPlayer.js +1 -170
  359. package/dist/components/themes/summer-sky/SummerSkyLayout.js +1 -221
  360. package/dist/components/themes/summer-sky/SummerSkySubmitModal.js +1 -18
  361. package/dist/components/themes/summer-sky/SummerSkyWaitingRoom.js +1 -194
  362. package/dist/components/themes/types.js +1 -1
  363. package/dist/components/ui/alert-dialog.js +1 -26
  364. package/dist/components/ui/alert.js +1 -22
  365. package/dist/components/ui/avatar.js +1 -12
  366. package/dist/components/ui/badge.js +1 -38
  367. package/dist/components/ui/button.js +1 -35
  368. package/dist/components/ui/calendar.js +1 -76
  369. package/dist/components/ui/card.js +1 -16
  370. package/dist/components/ui/carousel.js +1 -91
  371. package/dist/components/ui/checkbox.js +1 -9
  372. package/dist/components/ui/collapsible.js +1 -13
  373. package/dist/components/ui/command.js +1 -34
  374. package/dist/components/ui/confirm-dialog.js +1 -49
  375. package/dist/components/ui/dialog.js +1 -23
  376. package/dist/components/ui/drawer.js +1 -35
  377. package/dist/components/ui/dropdown-menu.js +1 -33
  378. package/dist/components/ui/file-upload.js +1 -155
  379. package/dist/components/ui/form.js +1 -62
  380. package/dist/components/ui/hover-card.js +1 -10
  381. package/dist/components/ui/image-preview.js +1 -29
  382. package/dist/components/ui/input.js +1 -8
  383. package/dist/components/ui/label.js +1 -9
  384. package/dist/components/ui/language-switcher.js +1 -15
  385. package/dist/components/ui/multi-select.js +1 -90
  386. package/dist/components/ui/points-input.js +1 -33
  387. package/dist/components/ui/popover.js +1 -26
  388. package/dist/components/ui/progress.js +1 -16
  389. package/dist/components/ui/radio-group.js +1 -12
  390. package/dist/components/ui/scroll-area.js +1 -11
  391. package/dist/components/ui/select.js +1 -38
  392. package/dist/components/ui/separator.js +1 -8
  393. package/dist/components/ui/sheet.js +1 -40
  394. package/dist/components/ui/single-select.js +1 -76
  395. package/dist/components/ui/skeleton.js +1 -6
  396. package/dist/components/ui/switch.js +1 -8
  397. package/dist/components/ui/table.js +1 -20
  398. package/dist/components/ui/tabs.js +1 -17
  399. package/dist/components/ui/textarea.js +1 -8
  400. package/dist/components/ui/tooltip.js +1 -11
  401. package/dist/index.d.ts +3 -3
  402. package/dist/index.js +1 -19
  403. package/dist/mcp/config.js +1 -32
  404. package/dist/mcp/http-client.js +1 -48
  405. package/dist/mcp/index.js +1 -29
  406. package/dist/mcp/tools/_helpers.js +1 -25
  407. package/dist/mcp/tools/exams.js +1 -439
  408. package/dist/mcp/tools/index.js +1 -6
  409. package/dist/mcp/tools/results.js +1 -19
  410. package/dist/shared/constants/ApiConstant.js +1 -276
  411. package/dist/shared/constants/QueryKeyConstant.js +1 -112
  412. package/dist/shared/constants/exam-level.enum.js +1 -41
  413. package/dist/shared/constants/landings/placement-test.constants.js +1 -431
  414. package/dist/shared/constants/question-skills.js +1 -267
  415. package/dist/shared/constants/routes.js +1 -81
  416. package/dist/shared/index.d.ts +3 -0
  417. package/dist/shared/index.js +1 -7
  418. package/dist/shared/lib/hooks/index.js +1 -10
  419. package/dist/shared/lib/hooks/useAlertDialog.js +1 -57
  420. package/dist/shared/lib/hooks/useAudioPlayCounter.js +1 -30
  421. package/dist/shared/lib/hooks/useAudioPreview.js +1 -81
  422. package/dist/shared/lib/hooks/useAudioUpload.js +1 -24
  423. package/dist/shared/lib/hooks/useConfirmDialog.js +1 -89
  424. package/dist/shared/lib/hooks/useDebouncedCallback.js +1 -32
  425. package/dist/shared/lib/hooks/useDelayedAutoPlay.js +1 -12
  426. package/dist/shared/lib/hooks/useFileUpload.js +1 -24
  427. package/dist/shared/lib/hooks/useImageUrl.js +1 -5
  428. package/dist/shared/lib/hooks/usePresignedFileUrl.js +1 -10
  429. package/dist/shared/lib/hooks/useReactHookForm.js +1 -31
  430. package/dist/shared/lib/hooks/useToast.js +1 -62
  431. package/dist/shared/lib/i18n/index.js +1 -54
  432. package/dist/shared/lib/i18n/messages/en/admin.js +1 -2104
  433. package/dist/shared/lib/i18n/messages/en/auth.js +1 -86
  434. package/dist/shared/lib/i18n/messages/en/common.js +1 -140
  435. package/dist/shared/lib/i18n/messages/en/file-upload.js +1 -18
  436. package/dist/shared/lib/i18n/messages/en/student.js +1 -133
  437. package/dist/shared/lib/i18n/messages/en/terms.js +1 -37
  438. package/dist/shared/lib/i18n/messages/en.js +1 -15
  439. package/dist/shared/lib/i18n/messages/vi/admin.js +1 -2104
  440. package/dist/shared/lib/i18n/messages/vi/auth.js +1 -86
  441. package/dist/shared/lib/i18n/messages/vi/common.js +1 -140
  442. package/dist/shared/lib/i18n/messages/vi/file-upload.js +1 -18
  443. package/dist/shared/lib/i18n/messages/vi/student.js +1 -133
  444. package/dist/shared/lib/i18n/messages/vi/terms.js +1 -37
  445. package/dist/shared/lib/i18n/messages/vi.js +1 -15
  446. package/dist/shared/lib/i18n/types.js +1 -1
  447. package/dist/shared/lib/stores/examQuestionStore.js +1 -212
  448. package/dist/shared/lib/stores/localeStore.js +1 -10
  449. package/dist/shared/lib/utils/public-page.js +1 -44
  450. package/dist/shared/lib/utils/question-reverse-transform.d.ts +6 -0
  451. package/dist/shared/lib/utils/question-reverse-transform.js +1 -0
  452. package/dist/shared/lib/utils/question-transform-types.js +1 -1
  453. package/dist/shared/lib/utils/question-transform-validation.d.ts +6 -0
  454. package/dist/shared/lib/utils/question-transform-validation.js +1 -0
  455. package/dist/shared/lib/utils/question-transform.d.ts +6 -2
  456. package/dist/shared/lib/utils/question-transform.js +1 -174
  457. package/dist/shared/lib/utils.js +1 -168
  458. package/dist/shared/lib/validation/choose-correct-answer.validation.js +1 -111
  459. package/dist/shared/lib/validation/label-the-picture.validation.js +1 -19
  460. package/dist/shared/lib/validation/listen-and-choose-from-answer-group.validation.js +1 -46
  461. package/dist/shared/lib/validation/listen-and-choose-objects-in-scene.validation.js +1 -21
  462. package/dist/shared/lib/validation/listen-and-drag-objects-into-scene.validation.js +1 -34
  463. package/dist/shared/lib/validation/listen-and-speak-answer.validation.js +1 -40
  464. package/dist/shared/lib/validation/listen-and-speak-compare-images.validation.js +1 -45
  465. package/dist/shared/lib/validation/listen-and-speak-image-group.validation.js +1 -19
  466. package/dist/shared/lib/validation/listen-and-speak-info-exchange.validation.js +1 -39
  467. package/dist/shared/lib/validation/listen-and-speak-odd-one-out.validation.js +1 -32
  468. package/dist/shared/lib/validation/listen-and-speak-question-list.validation.js +1 -19
  469. package/dist/shared/lib/validation/listen-and-speak-with-story-images.validation.js +1 -28
  470. package/dist/shared/lib/validation/look-picture-choose-correct-answer.validation.js +1 -12
  471. package/dist/shared/lib/validation/look-picture-fill-blank-choose-answer.validation.js +1 -47
  472. package/dist/shared/lib/validation/read-and-color-objects.validation.js +1 -29
  473. package/dist/shared/lib/validation/read-displayed-content.validation.js +1 -28
  474. package/dist/shared/lib/validation/speaking-conversation.validation.js +1 -75
  475. package/dist/shared/lib/validation/speaking-describe-image.validation.js +1 -31
  476. package/dist/shared/lib/validation/spontaneous-qa.validation.js +1 -48
  477. package/dist/shared/lib/validation/word-fill-paragraph-default.validation.js +1 -38
  478. package/dist/shared/lib/validation/word-fill-paragraph-with-options.validation.js +1 -39
  479. package/dist/shared/lib/validation/word-fill-paragraph.validation.js +1 -43
  480. package/dist/shared/lib/validation/word-ordering.validation.js +1 -33
  481. package/dist/shared/types/branch.types.js +1 -6
  482. package/dist/shared/types/common.types.js +1 -50
  483. package/dist/shared/types/entities/exam-schedule.types.js +1 -5
  484. package/dist/shared/types/entities/exam.types.js +1 -1
  485. package/dist/shared/types/entities/question.types.js +1 -4
  486. package/dist/shared/types/entities/result.types.js +1 -4
  487. package/dist/shared/types/entities/user.types.js +1 -4
  488. package/dist/shared/types/exam-taking.types.js +1 -2
  489. package/dist/shared/types/history.types.js +1 -34
  490. package/dist/shared/types/index.js +1 -1
  491. package/dist/shared/types/questions/choose-correct-adjective.js +1 -4
  492. package/dist/shared/types/questions/choose-the-correct-answer.js +1 -9
  493. package/dist/shared/types/questions/fill-missing-words-in-grid.js +1 -11
  494. package/dist/shared/types/questions/index.js +1 -40
  495. package/dist/shared/types/questions/listen-and-speak-answer.js +1 -1
  496. package/dist/shared/types/questions/listen-and-speak-compare-images.js +1 -1
  497. package/dist/shared/types/questions/listen-and-speak-image-group.js +1 -1
  498. package/dist/shared/types/questions/listen-and-speak-info-exchange.js +1 -3
  499. package/dist/shared/types/questions/listen-and-speak-odd-one-out.js +1 -3
  500. package/dist/shared/types/questions/listen-and-speak-question-list.js +1 -1
  501. package/dist/shared/types/questions/listen-and-speak-with-story-images.js +1 -3
  502. package/dist/shared/types/questions/look-picture-choose-correct-answer.js +1 -5
  503. package/dist/shared/types/questions/look-picture-fill-blank-choose-answer.js +1 -9
  504. package/dist/shared/types/questions/look-picture-fill-word-hint.js +1 -7
  505. package/dist/shared/types/questions/read-displayed-content.js +1 -1
  506. package/dist/shared/types/questions/speaking-conversation.js +1 -7
  507. package/dist/shared/types/questions/speaking-describe-image.js +1 -1
  508. package/dist/shared/types/questions/spontaneous-qa.js +1 -1
  509. package/dist/shared/types/questions/write-correct-verb-form.js +1 -12
  510. package/dist/shared/types/role.types.js +1 -1
  511. package/dist/shared/types/structure-management.js +1 -28
  512. package/dist/shared/types/student.types.js +1 -54
  513. package/dist/shared/types/teacher-dashboard-rooms.js +1 -2
  514. package/package.json +12 -3
@@ -1,2 +1 @@
1
- export * from './FillMissingWordsInGridCreator';
2
- export * from './FillMissingWordsInGridClient';
1
+ export*from"./FillMissingWordsInGridCreator";export*from"./FillMissingWordsInGridClient";
@@ -1,37 +1 @@
1
- import { FillMissingWordsInGridCreator, WriteMissingLettersCreator } from './FillMissingWordsInGridCreator';
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 = (question) => {
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 { GNSpeakingInterviewCreator } from './GNSpeakingInterviewCreator';
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 = (question) => {
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
- 'use client';
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
- 'use client';
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 * from './ImageObjectMatchingCreator';
2
- export * from './ImageObjectMatchingClient';
1
+ export*from"./ImageObjectMatchingCreator";export*from"./ImageObjectMatchingClient";