@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,439 +1 @@
1
- import { mkdir, writeFile } from 'node:fs/promises';
2
- import { dirname, resolve } from 'node:path';
3
- import { z } from 'zod';
4
- import { mcpRequest } from '../http-client.js';
5
- import { buildToolResult, unwrapData, wrapHandler } from './_helpers.js';
6
- const questionSchema = z.record(z.string(), z.unknown()).describe('Full API question payload or short form: { question, options, answer, points?, partId?, partNo? }.');
7
- const partSchema = z.record(z.string(), z.unknown()).describe('Part payload: { name?, partId?, questions: [...] }.');
8
- const templateSchema = z.record(z.string(), z.unknown()).describe('Template payload accepted by PATCH /manage/exams/:id/template.');
9
- const examLevelSchema = z.enum(['PRE_A1', 'A1', 'A2', 'B1', 'B2', 'C1', 'C2']);
10
- export function registerExamTools(server) {
11
- server.registerTool('exam_templates_search', {
12
- title: 'Search exam templates',
13
- description: 'Search available OE Exam templates. Use this before exam_create_with_questions to choose examTemplateId.',
14
- inputSchema: {
15
- search: z.string().optional().describe('Template name keyword.'),
16
- page: z.number().int().positive().default(1).describe('1-based page number.'),
17
- limit: z.number().int().positive().max(50).default(9).describe('Page size sent as API size.'),
18
- },
19
- }, wrapHandler('exam_templates_search', async (input) => {
20
- const query = new URLSearchParams({ page: String(input.page ?? 1), size: String(input.limit ?? 9) });
21
- if (input.search)
22
- query.set('filter', `name:like:${input.search}`);
23
- const result = await mcpRequest({ method: 'GET', url: `/api/v1/exam-templates?${query}` });
24
- return buildToolResult({ templates: unwrapList(result) });
25
- }));
26
- server.registerTool('exam_template_get', {
27
- title: 'Get exam template',
28
- description: 'Get one OE Exam template by id. The returned templatePayload can be passed as template to exam_create_with_questions.',
29
- inputSchema: {
30
- id: z.string().min(1).describe('Template id from exam_templates_search.'),
31
- },
32
- }, wrapHandler('exam_template_get', async (input) => {
33
- const template = unwrapData(await mcpRequest({ method: 'GET', url: `/api/v1/exam-templates/${encodeURIComponent(String(input.id))}` }));
34
- return buildToolResult({ template, templatePayload: toTemplatePayload(template) });
35
- }));
36
- server.registerTool('exam_question_template_get', {
37
- title: 'Get question payload template',
38
- description: 'Return easy-to-fill question JSON templates for exam_create_with_questions. Use this before creating questions if you need the expected shape.',
39
- inputSchema: {
40
- templateId: z.string().min(1).optional().describe('Template id. If provided, generate question payloads from template.parts.'),
41
- type: z.string().default('CHOOSE_THE_CORRECT_ANSWER').describe('Fallback question type when templateId is omitted.'),
42
- count: z.number().int().positive().max(100).default(1).describe('Fallback count when templateId is omitted.'),
43
- partId: z.union([z.string(), z.number()]).default(1),
44
- partNo: z.number().int().positive().default(1),
45
- skill: z.string().default('READING'),
46
- },
47
- }, wrapHandler('exam_question_template_get', async (input) => {
48
- const template = typeof input.templateId === 'string'
49
- ? unwrapData(await mcpRequest({ method: 'GET', url: `/api/v1/exam-templates/${encodeURIComponent(input.templateId)}` }))
50
- : undefined;
51
- return buildToolResult({
52
- usage: 'Pass questions directly into exam_create_with_questions.questions. Fill content/correctAnswer for each generated API-format payload.',
53
- templateId: template?.id,
54
- questions: template ? questionsFromTemplate(template) : fallbackQuestions(input),
55
- });
56
- }));
57
- server.registerTool('exam_json_template_generate', {
58
- title: 'Get exam JSON template',
59
- description: 'Generate a user-friendly examJson skeleton from templateTitle or templateId. Edit this JSON, then pass it to exam_create_from_json.',
60
- inputSchema: {
61
- templateTitle: z.string().optional().describe('Template title to search, e.g. Achievers 2026 Round 1.'),
62
- templateId: z.string().optional().describe('Exact template id. Preferred when known.'),
63
- name: z.string().optional().describe('Exam name to put in the JSON.'),
64
- examLevel: examLevelSchema.optional().describe('Optional exam level for the generated JSON.'),
65
- difficulty: z.enum(['EASY', 'MEDIUM', 'HARD']).optional().describe('Optional difficulty for the generated JSON.'),
66
- durationMinutes: z.number().int().positive().optional().describe('Optional duration for the generated JSON.'),
67
- },
68
- }, wrapHandler('exam_json_template_generate', async (input) => {
69
- const template = await resolveTemplate({ templateId: input.templateId, templateTitle: input.templateTitle });
70
- const examJson = examJsonTemplate(template, input);
71
- const examJsonString = JSON.stringify(examJson, null, 2);
72
- const filePath = resolve(`${slugFromTitle(String(input.name ?? template.title ?? template.name ?? 'exam'))}.json`);
73
- await mkdir(dirname(filePath), { recursive: true });
74
- await writeFile(filePath, `${examJsonString}\n`, 'utf8');
75
- return buildToolResult({
76
- usage: 'Edit filePath, then call exam_create_from_json with the edited JSON string.',
77
- templateId: template.id,
78
- templateTitle: template.title ?? template.name,
79
- filePath,
80
- examJson,
81
- examJsonString,
82
- });
83
- }));
84
- server.registerTool('exam_create_from_json', {
85
- title: 'Create exam from JSON',
86
- description: 'Create an exam from a user-friendly JSON payload grouped by parts. Searches template by title, patches template, maps questions to template.parts, then creates questions.',
87
- inputSchema: {
88
- examJson: z.string().optional().describe('JSON string with { name, templateTitle/templateId, parts: [{ questions: [...] }] }. Use this for copy/paste.'),
89
- exam: z.record(z.string(), z.unknown()).optional().describe('Same payload as object if the client supports object arguments.'),
90
- },
91
- }, wrapHandler('exam_create_from_json', async (input) => {
92
- const examInput = parseExamJsonInput(input.examJson, input.exam);
93
- const template = await resolveTemplate(examInput);
94
- const questions = questionsFromExamJson(examInput, template);
95
- const payload = examPayloadFromJson(examInput, template.id, questions);
96
- const exam = unwrapData(await mcpRequest({ method: 'POST', url: '/api/v1/manage/exams', body: payload.exam }));
97
- await mcpRequest({ method: 'PATCH', url: `/api/v1/manage/exams/${encodeURIComponent(exam.id)}/template`, body: { template: toTemplatePayload(template) } });
98
- const savedQuestions = questions.length === 0 ? null : await mcpRequest({ method: 'POST', url: '/api/v1/manage/questions/batch', body: { examId: exam.id, questions } });
99
- return buildToolResult({ exam, templateId: template.id, questions: savedQuestions, generatedQuestionCount: questions.length });
100
- }));
101
- server.registerTool('exam_create_with_questions', {
102
- title: 'Create exam with questions',
103
- description: 'Create an exam, optionally patch template, then bulk create questions via /api/v1/manage/questions/batch. If you do not know examTemplateId/template, call exam_templates_search then exam_template_get first.',
104
- inputSchema: {
105
- name: z.string().min(1),
106
- code: z.string().min(1).optional(),
107
- examLevel: examLevelSchema.optional().describe('Valid exam level only: PRE_A1, A1, A2, B1, B2, C1, C2. Do not use template title/id here.'),
108
- difficulty: z.enum(['EASY', 'MEDIUM', 'HARD']).optional(),
109
- totalScore: z.number().nonnegative().optional(),
110
- durationMinutes: z.number().int().positive().optional(),
111
- description: z.string().optional(),
112
- isComplete: z.boolean().optional(),
113
- isActive: z.boolean().optional(),
114
- examTemplateId: z.string().min(1).optional(),
115
- template: templateSchema.optional(),
116
- questions: z.array(questionSchema).default([]),
117
- },
118
- }, wrapHandler('exam_create_with_questions', async (input) => {
119
- const { template, questions, ...examPayload } = input;
120
- const exam = unwrapData(await mcpRequest({ method: 'POST', url: '/api/v1/manage/exams', body: clean(examPayload) }));
121
- const templatePayload = isNonEmptyRecord(template) ? template : await getTemplatePayload(examPayload.examTemplateId);
122
- if (isNonEmptyRecord(templatePayload)) {
123
- await mcpRequest({ method: 'PATCH', url: `/api/v1/manage/exams/${encodeURIComponent(exam.id)}/template`, body: { template: templatePayload } });
124
- }
125
- const normalizedQuestions = questions.map(normalizeQuestion);
126
- const savedQuestions = normalizedQuestions.length === 0 ? null : await mcpRequest({ method: 'POST', url: '/api/v1/manage/questions/batch', body: { examId: exam.id, questions: normalizedQuestions } });
127
- return buildToolResult({ exam, questions: savedQuestions });
128
- }));
129
- }
130
- function clean(input) {
131
- return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
132
- }
133
- function isNonEmptyRecord(value) {
134
- return Boolean(value && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length > 0);
135
- }
136
- function parseExamJsonInput(examJson, exam) {
137
- if (isNonEmptyRecord(exam))
138
- return exam;
139
- if (typeof examJson !== 'string' || !examJson.trim())
140
- throw new Error('examJson or exam is required');
141
- const parsed = JSON.parse(examJson);
142
- if (!isNonEmptyRecord(parsed))
143
- throw new Error('examJson must be a JSON object');
144
- return parsed;
145
- }
146
- async function resolveTemplate(input) {
147
- const templateId = stringOrUndefined(input.templateId);
148
- if (templateId)
149
- return unwrapData(await mcpRequest({ method: 'GET', url: `/api/v1/exam-templates/${encodeURIComponent(templateId)}` }));
150
- const templateTitle = stringOrUndefined(input.templateTitle);
151
- if (!templateTitle)
152
- throw new Error('templateTitle or templateId is required');
153
- const query = new URLSearchParams({ page: '1', size: '10', filter: `name:like:${templateTitle}` });
154
- const templates = unwrapList(await mcpRequest({ method: 'GET', url: `/api/v1/exam-templates?${query}` }));
155
- const exact = templates.find((template) => (template.title ?? template.name) === templateTitle);
156
- const template = exact ?? templates[0];
157
- if (!template?.id)
158
- throw new Error(`No exam template found for title: ${templateTitle}`);
159
- return unwrapData(await mcpRequest({ method: 'GET', url: `/api/v1/exam-templates/${encodeURIComponent(template.id)}` }));
160
- }
161
- function examPayloadFromJson(input, templateId, questions) {
162
- return {
163
- exam: clean({
164
- name: stringValue(input.name, 'New OE Exam'),
165
- code: stringOrUndefined(input.code) ?? codeFromName(stringValue(input.name, 'New OE Exam')),
166
- totalScore: numberValue(input.totalScore, questions.length || 100),
167
- description: stringOrUndefined(input.description) ?? stringOrUndefined(input.templateTitle) ?? '',
168
- isComplete: booleanValue(input.isComplete, false),
169
- isActive: booleanValue(input.isActive, false),
170
- examTemplateId: templateId,
171
- examLevel: validExamLevel(input.examLevel),
172
- difficulty: validDifficulty(input.difficulty),
173
- durationMinutes: numberOrUndefined(input.durationMinutes),
174
- }),
175
- };
176
- }
177
- function examJsonTemplate(template, options) {
178
- const parts = Array.isArray(template.parts) ? template.parts.filter(isNonEmptyRecord) : [];
179
- return {
180
- name: stringValue(options.name, `${template.title ?? template.name ?? 'OE Exam'} Draft`),
181
- templateId: template.id,
182
- templateTitle: template.title ?? template.name,
183
- totalScore: numberValue(template.totalScore ?? template.total_questions, totalQuestionCount(parts)),
184
- examLevel: validExamLevel(options.examLevel) ?? validExamLevel(template.examLevel ?? template.exam_level ?? template.level) ?? 'A1',
185
- difficulty: validDifficulty(options.difficulty) ?? 'MEDIUM',
186
- durationMinutes: numberValue(options.durationMinutes, 45),
187
- isComplete: false,
188
- isActive: false,
189
- parts: parts.map((part, index) => examJsonPartTemplate(part, index)),
190
- };
191
- }
192
- function examJsonPartTemplate(part, index) {
193
- const startIndex = numberValue(part.startIndex, 0);
194
- const count = numberValue(part.questionCount, numberValue(part.endIndex, startIndex) - startIndex + 1);
195
- return {
196
- id: part.id ?? index + 1,
197
- name: part.name ?? `Part ${index + 1}`,
198
- questionType: stringValue(part.questionType, 'CHOOSE_THE_CORRECT_ANSWER'),
199
- skill: stringValue(part.skill, 'READING'),
200
- questions: Array.from({ length: Math.max(count, 0) }, (_, offset) => simpleQuestionTemplate(part, startIndex + offset + 1)),
201
- };
202
- }
203
- function simpleQuestionTemplate(part, questionNumber) {
204
- const questionType = stringValue(part.questionType, 'CHOOSE_THE_CORRECT_ANSWER');
205
- const options = (Array.isArray(part.defaultValue) && part.defaultValue.length > 0
206
- ? part.defaultValue
207
- : Array.from({ length: numberValue(part.optionCount, 4) }, (_, index) => String.fromCharCode(65 + index))).map(String);
208
- if (questionType.includes('FILL') || questionType.includes('WRITE'))
209
- return { question: `Question ${questionNumber}`, answer: '' };
210
- if (questionType.includes('SPEAK'))
211
- return { prompt: `Question ${questionNumber}`, expectedAnswer: '' };
212
- return { question: `Question ${questionNumber}`, options, answer: options[0] ?? 'A' };
213
- }
214
- function totalQuestionCount(parts) {
215
- return parts.reduce((total, part) => total + numberValue(part.questionCount, 0), 0);
216
- }
217
- function questionsFromExamJson(input, template) {
218
- const templateParts = Array.isArray(template.parts) ? template.parts.filter(isNonEmptyRecord) : [];
219
- const parts = Array.isArray(input.parts) ? input.parts.filter(isNonEmptyRecord) : [{ questions: input.questions }];
220
- return parts.flatMap((part, partIndex) => {
221
- const templatePart = findTemplatePart(part, templateParts, partIndex);
222
- const questions = Array.isArray(part.questions) ? part.questions.filter(isNonEmptyRecord) : [];
223
- return questions.map((question, questionIndex) => questionFromPart(question, templatePart, partIndex, questionIndex));
224
- });
225
- }
226
- function findTemplatePart(part, templateParts, index) {
227
- const partId = stringOrUndefined(part.partId ?? part.id);
228
- const name = stringOrUndefined(part.name);
229
- return templateParts.find((item) => String(item.id) === partId)
230
- ?? templateParts.find((item) => typeof item.name === 'string' && item.name === name)
231
- ?? templateParts[index]
232
- ?? { id: index + 1, questionType: 'CHOOSE_THE_CORRECT_ANSWER', skill: 'READING', optionCount: 4 };
233
- }
234
- function questionFromPart(question, part, partIndex, questionIndex) {
235
- const startIndex = numberValue(part.startIndex, 0);
236
- const base = questionPayload({
237
- questionType: stringValue(question.questionType ?? question.type ?? part.questionType, 'CHOOSE_THE_CORRECT_ANSWER'),
238
- questionNumber: numberValue(question.questionNumber, startIndex + questionIndex + 1),
239
- partId: String(question.partId ?? part.id ?? partIndex + 1),
240
- partNo: numberValue(question.partNo, partIndex + 1),
241
- sectionId: numberOrUndefined(question.sectionId ?? part.sectionId),
242
- skill: stringValue(question.questionSkillType ?? question.skill ?? part.skill, 'READING'),
243
- optionCount: numberValue(part.optionCount, 4),
244
- defaultValue: Array.isArray(part.defaultValue) ? part.defaultValue : undefined,
245
- });
246
- return normalizeQuestion(applySimpleQuestionFields({ ...base, ...question }), questionIndex);
247
- }
248
- function applySimpleQuestionFields(question) {
249
- const content = isNonEmptyRecord(question.content) ? { ...question.content } : {};
250
- if (typeof question.question === 'string')
251
- content.question = question.question;
252
- if (typeof question.text === 'string')
253
- content.question = question.text;
254
- if (Array.isArray(question.options)) {
255
- content.optionType = content.optionType ?? 'text';
256
- content.options = question.options;
257
- }
258
- return clean({
259
- ...question,
260
- content: isNonEmptyRecord(content) ? content : question.content,
261
- correctAnswer: question.answer === undefined && question.expectedAnswer === undefined
262
- ? question.correctAnswer
263
- : simpleCorrectAnswer(question.questionType, question.answer ?? question.expectedAnswer, content.options),
264
- });
265
- }
266
- function simpleCorrectAnswer(questionType, answer, options) {
267
- if (answer === undefined)
268
- return undefined;
269
- const type = String(questionType ?? '');
270
- if (type.includes('FILL') || type.includes('WRITE'))
271
- return { answers: [String(answer)] };
272
- if (type.includes('SPEAK'))
273
- return { expectedAnswer: String(answer) };
274
- return { answer: answerIndexFromOptions(answer, options) };
275
- }
276
- function answerIndexFromOptions(answer, options) {
277
- if (typeof answer === 'string' && Array.isArray(options)) {
278
- const index = options.map(String).findIndex((option) => option === answer);
279
- if (index >= 0)
280
- return index;
281
- }
282
- return answerIndex(answer);
283
- }
284
- function codeFromName(name) {
285
- return name.toUpperCase().replace(/[^A-Z0-9]+/g, '_').replace(/^_|_$/g, '').slice(0, 40) || 'OE_EXAM';
286
- }
287
- function slugFromTitle(title) {
288
- return title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 80) || 'exam';
289
- }
290
- function stringOrUndefined(value) {
291
- return typeof value === 'string' && value ? value : undefined;
292
- }
293
- function booleanValue(value, fallback) {
294
- return typeof value === 'boolean' ? value : fallback;
295
- }
296
- function validExamLevel(value) {
297
- return typeof value === 'string' && examLevelSchema.safeParse(value).success ? value : undefined;
298
- }
299
- function validDifficulty(value) {
300
- return typeof value === 'string' && ['EASY', 'MEDIUM', 'HARD'].includes(value) ? value : undefined;
301
- }
302
- function normalizeQuestion(question, index) {
303
- if (question.questionType)
304
- return clean({
305
- ...question,
306
- partId: question.partId === undefined ? undefined : String(question.partId),
307
- partNo: question.partNo ?? 1,
308
- questionNumber: question.questionNumber ?? index + 1,
309
- points: question.points ?? 1,
310
- addToQuestionBank: question.addToQuestionBank ?? false,
311
- isExample: question.isExample ?? false,
312
- });
313
- const options = Array.isArray(question.options) ? question.options : ['A', 'B', 'C', 'D'];
314
- return clean({
315
- questionType: question.type ?? 'CHOOSE_THE_CORRECT_ANSWER',
316
- content: question.content ?? {
317
- question: String(question.question ?? question.text ?? `Question ${index + 1}`),
318
- optionType: 'text',
319
- options,
320
- },
321
- correctAnswer: question.correctAnswer ?? { answer: answerIndex(question.answer ?? question.correctAnswerIndex ?? 0) },
322
- points: question.points ?? 1,
323
- explanation: question.explanation ?? '',
324
- questionNumber: question.questionNumber ?? index + 1,
325
- partId: String(question.partId ?? 1),
326
- partNo: question.partNo ?? 1,
327
- sectionId: question.sectionId,
328
- questionSkillType: question.questionSkillType ?? question.skill ?? 'READING',
329
- addToQuestionBank: question.addToQuestionBank ?? false,
330
- isExample: question.isExample ?? false,
331
- });
332
- }
333
- function answerIndex(value) {
334
- if (typeof value === 'number')
335
- return value;
336
- if (typeof value === 'string' && /^[A-E]$/i.test(value))
337
- return value.toUpperCase().charCodeAt(0) - 65;
338
- const parsed = Number(value);
339
- return Number.isFinite(parsed) ? parsed : 0;
340
- }
341
- function fallbackQuestions(input) {
342
- return Array.from({ length: Number(input.count ?? 1) }, (_, index) => questionPayload({
343
- questionType: String(input.type ?? 'CHOOSE_THE_CORRECT_ANSWER'),
344
- questionNumber: index + 1,
345
- partId: String(input.partId ?? 1),
346
- partNo: Number(input.partNo ?? 1),
347
- skill: String(input.skill ?? 'READING'),
348
- }));
349
- }
350
- function questionsFromTemplate(template) {
351
- const parts = Array.isArray(template.parts) ? template.parts.filter(isNonEmptyRecord) : [];
352
- return parts.flatMap((part, partIndex) => {
353
- const startIndex = numberValue(part.startIndex, 0);
354
- const count = numberValue(part.questionCount, numberValue(part.endIndex, startIndex) - startIndex + 1);
355
- return Array.from({ length: Math.max(count, 0) }, (_, offset) => questionPayload({
356
- questionType: stringValue(part.questionType, 'CHOOSE_THE_CORRECT_ANSWER'),
357
- questionNumber: startIndex + offset + 1,
358
- partId: String(part.id ?? partIndex + 1),
359
- partNo: partIndex + 1,
360
- sectionId: numberOrUndefined(part.sectionId),
361
- skill: stringValue(part.skill, 'READING'),
362
- optionCount: numberValue(part.optionCount, 4),
363
- defaultValue: Array.isArray(part.defaultValue) ? part.defaultValue : undefined,
364
- }));
365
- });
366
- }
367
- function questionPayload(input) {
368
- return clean({
369
- questionType: input.questionType,
370
- content: contentTemplate(input.questionType, input.questionNumber, input.optionCount, input.defaultValue),
371
- correctAnswer: correctAnswerTemplate(input.questionType),
372
- points: 1,
373
- explanation: '',
374
- questionNumber: input.questionNumber,
375
- partId: input.partId,
376
- partNo: input.partNo,
377
- sectionId: input.sectionId,
378
- questionSkillType: input.skill,
379
- addToQuestionBank: false,
380
- isExample: false,
381
- });
382
- }
383
- function contentTemplate(questionType, questionNumber, optionCount = 4, defaultValue) {
384
- const options = (defaultValue?.length ? defaultValue : Array.from({ length: optionCount }, (_, index) => String.fromCharCode(65 + index))).map(String);
385
- if (questionType.includes('FILL') || questionType.includes('WRITE'))
386
- return { question: `Question ${questionNumber}`, text: '' };
387
- if (questionType.includes('SPEAK'))
388
- return { prompt: `Question ${questionNumber}` };
389
- if (questionType.includes('LISTEN'))
390
- return { question: `Question ${questionNumber}`, audioUrl: '', optionType: 'text', options };
391
- return { question: `Question ${questionNumber}`, optionType: 'text', options };
392
- }
393
- function correctAnswerTemplate(questionType) {
394
- if (questionType.includes('FILL') || questionType.includes('WRITE'))
395
- return { answers: [''] };
396
- if (questionType.includes('SPEAK'))
397
- return { expectedAnswer: '' };
398
- return { answer: 0 };
399
- }
400
- function stringValue(value, fallback) {
401
- return typeof value === 'string' && value ? value : fallback;
402
- }
403
- function numberValue(value, fallback) {
404
- const parsed = Number(value);
405
- return Number.isFinite(parsed) ? parsed : fallback;
406
- }
407
- function numberOrUndefined(value) {
408
- const parsed = Number(value);
409
- return Number.isFinite(parsed) ? parsed : undefined;
410
- }
411
- function unwrapList(value) {
412
- const data = unwrapData(value);
413
- if (Array.isArray(data))
414
- return data;
415
- if (data && typeof data === 'object' && 'data' in data && Array.isArray(data.data))
416
- return data.data;
417
- return [];
418
- }
419
- async function getTemplatePayload(id) {
420
- if (typeof id !== 'string' || !id)
421
- return undefined;
422
- const template = unwrapData(await mcpRequest({ method: 'GET', url: `/api/v1/exam-templates/${encodeURIComponent(id)}` }));
423
- return toTemplatePayload(template);
424
- }
425
- function toTemplatePayload(template) {
426
- const parts = Array.isArray(template.parts) ? template.parts : [];
427
- return clean({
428
- id: template.id,
429
- title: template.title ?? template.name,
430
- subTitle: template.subTitle ?? template.sub_title ?? '',
431
- theme: template.theme,
432
- totalQuestions: template.totalQuestions ?? template.total_questions,
433
- totalParts: template.totalParts ?? template.total_parts ?? parts.length,
434
- description: template.description ?? '',
435
- examLevel: template.examLevel ?? template.exam_level ?? template.level ?? '',
436
- type: template.type ?? null,
437
- parts,
438
- });
439
- }
1
+ import{mkdir as e,writeFile as t}from"node:fs/promises";import{dirname as n,resolve as i}from"node:path";import{z as a}from"zod";import{mcpRequest as o}from"../http-client.js";import{buildToolResult as r,unwrapData as s,wrapHandler as l}from"./_helpers.js";const p=a.record(a.string(),a.unknown()).describe("Full API question payload or short form: { question, options, answer, points?, partId?, partNo? }."),u=(a.record(a.string(),a.unknown()).describe("Part payload: { name?, partId?, questions: [...] }."),a.record(a.string(),a.unknown()).describe("Template payload accepted by PATCH /manage/exams/:id/template.")),m=a.enum(["PRE_A1","A1","A2","B1","B2","C1","C2"]);export function registerExamTools(g){g.registerTool("exam_templates_search",{title:"Search exam templates",description:"Search available OE Exam templates. Use this before exam_create_with_questions to choose examTemplateId.",inputSchema:{search:a.string().optional().describe("Template name keyword."),page:a.number().int().positive().default(1).describe("1-based page number."),limit:a.number().int().positive().max(50).default(9).describe("Page size sent as API size.")}},l("exam_templates_search",async e=>{const t=new URLSearchParams({page:String(e.page??1),size:String(e.limit??9)});e.search&&t.set("filter",`name:like:${e.search}`);const n=await o({method:"GET",url:`/api/v1/exam-templates?${t}`});return r({templates:k(n)})})),g.registerTool("exam_template_get",{title:"Get exam template",description:"Get one OE Exam template by id. The returned templatePayload can be passed as template to exam_create_with_questions.",inputSchema:{id:a.string().min(1).describe("Template id from exam_templates_search.")}},l("exam_template_get",async e=>{const t=s(await o({method:"GET",url:`/api/v1/exam-templates/${encodeURIComponent(String(e.id))}`}));return r({template:t,templatePayload:P(t)})})),g.registerTool("exam_question_template_get",{title:"Get question payload template",description:"Return easy-to-fill question JSON templates for exam_create_with_questions. Use this before creating questions if you need the expected shape.",inputSchema:{templateId:a.string().min(1).optional().describe("Template id. If provided, generate question payloads from template.parts."),type:a.string().default("CHOOSE_THE_CORRECT_ANSWER").describe("Fallback question type when templateId is omitted."),count:a.number().int().positive().max(100).default(1).describe("Fallback count when templateId is omitted."),partId:a.union([a.string(),a.number()]).default(1),partNo:a.number().int().positive().default(1),skill:a.string().default("READING")}},l("exam_question_template_get",async e=>{const t="string"==typeof e.templateId?s(await o({method:"GET",url:`/api/v1/exam-templates/${encodeURIComponent(e.templateId)}`})):void 0;return r({usage:"Pass questions directly into exam_create_with_questions.questions. Fill content/correctAnswer for each generated API-format payload.",templateId:t?.id,questions:t?b(t):S(e)})})),g.registerTool("exam_json_template_generate",{title:"Get exam JSON template",description:"Generate a user-friendly examJson skeleton from templateTitle or templateId. Edit this JSON, then pass it to exam_create_from_json.",inputSchema:{templateTitle:a.string().optional().describe("Template title to search, e.g. Achievers 2026 Round 1."),templateId:a.string().optional().describe("Exact template id. Preferred when known."),name:a.string().optional().describe("Exam name to put in the JSON."),examLevel:m.optional().describe("Optional exam level for the generated JSON."),difficulty:a.enum(["EASY","MEDIUM","HARD"]).optional().describe("Optional difficulty for the generated JSON."),durationMinutes:a.number().int().positive().optional().describe("Optional duration for the generated JSON.")}},l("exam_json_template_generate",async a=>{const o=await f({templateId:a.templateId,templateTitle:a.templateTitle}),s=function(e,t){const n=Array.isArray(e.parts)?e.parts.filter(c):[];return{name:v(t.name,`${e.title??e.name??"OE Exam"} Draft`),templateId:e.id,templateTitle:e.title??e.name,totalScore:O(e.totalScore??e.total_questions,y(n)),examLevel:_(t.examLevel)??_(e.examLevel??e.exam_level??e.level)??"A1",difficulty:E(t.difficulty)??"MEDIUM",durationMinutes:O(t.durationMinutes,45),isComplete:!1,isActive:!1,parts:n.map((e,t)=>function(e,t){const n=O(e.startIndex,0),i=O(e.questionCount,O(e.endIndex,n)-n+1);return{id:e.id??t+1,name:e.name??`Part ${t+1}`,questionType:v(e.questionType,"CHOOSE_THE_CORRECT_ANSWER"),skill:v(e.skill,"READING"),questions:Array.from({length:Math.max(i,0)},(t,i)=>function(e,t){const n=v(e.questionType,"CHOOSE_THE_CORRECT_ANSWER"),i=(Array.isArray(e.defaultValue)&&e.defaultValue.length>0?e.defaultValue:Array.from({length:O(e.optionCount,4)},(e,t)=>String.fromCharCode(65+t))).map(String);return n.includes("FILL")||n.includes("WRITE")?{question:`Question ${t}`,answer:""}:n.includes("SPEAK")?{prompt:`Question ${t}`,expectedAnswer:""}:{question:`Question ${t}`,options:i,answer:i[0]??"A"}}(e,n+i+1))}}(e,t))}}(o,a),l=JSON.stringify(s,null,2),p=i(`${u=String(a.name??o.title??o.name??"exam"),u.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,80)||"exam"}.json`);var u;return await e(n(p),{recursive:!0}),await t(p,`${l}\n`,"utf8"),r({usage:"Edit filePath, then call exam_create_from_json with the edited JSON string.",templateId:o.id,templateTitle:o.title??o.name,filePath:p,examJson:s,examJsonString:l})})),g.registerTool("exam_create_from_json",{title:"Create exam from JSON",description:"Create an exam from a user-friendly JSON payload grouped by parts. Searches template by title, patches template, maps questions to template.parts, then creates questions.",inputSchema:{examJson:a.string().optional().describe("JSON string with { name, templateTitle/templateId, parts: [{ questions: [...] }] }. Use this for copy/paste."),exam:a.record(a.string(),a.unknown()).optional().describe("Same payload as object if the client supports object arguments.")}},l("exam_create_from_json",async e=>{const t=function(e,t){if(c(t))return t;if("string"!=typeof e||!e.trim())throw new Error("examJson or exam is required");const n=JSON.parse(e);if(!c(n))throw new Error("examJson must be a JSON object");return n}(e.examJson,e.exam),n=await f(t),i=function(e,t){const n=Array.isArray(t.parts)?t.parts.filter(c):[],i=Array.isArray(e.parts)?e.parts.filter(c):[{questions:e.questions}];return i.flatMap((e,t)=>{const i=function(e,t,n){const i=A(e.partId??e.id),a=A(e.name);return t.find(e=>String(e.id)===i)??t.find(e=>"string"==typeof e.name&&e.name===a)??t[n]??{id:n+1,questionType:"CHOOSE_THE_CORRECT_ANSWER",skill:"READING",optionCount:4}}(e,n,t);return(Array.isArray(e.questions)?e.questions.filter(c):[]).map((e,n)=>function(e,t,n,i){const a=O(t.startIndex,0),o=w({questionType:v(e.questionType??e.type??t.questionType,"CHOOSE_THE_CORRECT_ANSWER"),questionNumber:O(e.questionNumber,a+i+1),partId:String(e.partId??t.id??n+1),partNo:O(e.partNo,n+1),sectionId:R(e.sectionId??t.sectionId),skill:v(e.questionSkillType??e.skill??t.skill,"READING"),optionCount:O(t.optionCount,4),defaultValue:Array.isArray(t.defaultValue)?t.defaultValue:void 0});return q(function(e){const t=c(e.content)?{...e.content}:{};"string"==typeof e.question&&(t.question=e.question);"string"==typeof e.text&&(t.question=e.text);Array.isArray(e.options)&&(t.optionType=t.optionType??"text",t.options=e.options);return d({...e,content:c(t)?t:e.content,correctAnswer:void 0===e.answer&&void 0===e.expectedAnswer?e.correctAnswer:x(e.questionType,e.answer??e.expectedAnswer,t.options)})}({...o,...e}),i)}(e,i,t,n))})}(t,n),a=function(e,t,n){return{exam:d({name:v(e.name,"New OE Exam"),code:A(e.code)??h(v(e.name,"New OE Exam")),totalScore:O(e.totalScore,n.length||100),description:A(e.description)??A(e.templateTitle)??"",isComplete:T(e.isComplete,!1),isActive:T(e.isActive,!1),examTemplateId:t,examLevel:_(e.examLevel),difficulty:E(e.difficulty),durationMinutes:R(e.durationMinutes)})}}(t,n.id,i),l=s(await o({method:"POST",url:"/api/v1/manage/exams",body:a.exam}));await o({method:"PATCH",url:`/api/v1/manage/exams/${encodeURIComponent(l.id)}/template`,body:{template:P(n)}});const p=0===i.length?null:await o({method:"POST",url:"/api/v1/manage/questions/batch",body:{examId:l.id,questions:i}});return r({exam:l,templateId:n.id,questions:p,generatedQuestionCount:i.length})})),g.registerTool("exam_create_with_questions",{title:"Create exam with questions",description:"Create an exam, optionally patch template, then bulk create questions via /api/v1/manage/questions/batch. If you do not know examTemplateId/template, call exam_templates_search then exam_template_get first.",inputSchema:{name:a.string().min(1),code:a.string().min(1).optional(),examLevel:m.optional().describe("Valid exam level only: PRE_A1, A1, A2, B1, B2, C1, C2. Do not use template title/id here."),difficulty:a.enum(["EASY","MEDIUM","HARD"]).optional(),totalScore:a.number().nonnegative().optional(),durationMinutes:a.number().int().positive().optional(),description:a.string().optional(),isComplete:a.boolean().optional(),isActive:a.boolean().optional(),examTemplateId:a.string().min(1).optional(),template:u.optional(),questions:a.array(p).default([])}},l("exam_create_with_questions",async e=>{const{template:t,questions:n,...i}=e,a=s(await o({method:"POST",url:"/api/v1/manage/exams",body:d(i)})),l=c(t)?t:await async function(e){if("string"!=typeof e||!e)return;const t=s(await o({method:"GET",url:`/api/v1/exam-templates/${encodeURIComponent(e)}`}));return P(t)}(i.examTemplateId);c(l)&&await o({method:"PATCH",url:`/api/v1/manage/exams/${encodeURIComponent(a.id)}/template`,body:{template:l}});const p=n.map(q),u=0===p.length?null:await o({method:"POST",url:"/api/v1/manage/questions/batch",body:{examId:a.id,questions:p}});return r({exam:a,questions:u})}))}function d(e){return Object.fromEntries(Object.entries(e).filter(([,e])=>void 0!==e))}function c(e){return Boolean(e&&"object"==typeof e&&!Array.isArray(e)&&Object.keys(e).length>0)}async function f(e){const t=A(e.templateId);if(t)return s(await o({method:"GET",url:`/api/v1/exam-templates/${encodeURIComponent(t)}`}));const n=A(e.templateTitle);if(!n)throw new Error("templateTitle or templateId is required");const i=new URLSearchParams({page:"1",size:"10",filter:`name:like:${n}`}),a=k(await o({method:"GET",url:`/api/v1/exam-templates?${i}`})),r=a.find(e=>(e.title??e.name)===n),l=r??a[0];if(!l?.id)throw new Error(`No exam template found for title: ${n}`);return s(await o({method:"GET",url:`/api/v1/exam-templates/${encodeURIComponent(l.id)}`}))}function y(e){return e.reduce((e,t)=>e+O(t.questionCount,0),0)}function x(e,t,n){if(void 0===t)return;const i=String(e??"");return i.includes("FILL")||i.includes("WRITE")?{answers:[String(t)]}:i.includes("SPEAK")?{expectedAnswer:String(t)}:{answer:g(t,n)}}function g(e,t){if("string"==typeof e&&Array.isArray(t)){const n=t.map(String).findIndex(t=>t===e);if(n>=0)return n}return I(e)}function h(e){return e.toUpperCase().replace(/[^A-Z0-9]+/g,"_").replace(/^_|_$/g,"").slice(0,40)||"OE_EXAM"}function A(e){return"string"==typeof e&&e?e:void 0}function T(e,t){return"boolean"==typeof e?e:t}function _(e){return"string"==typeof e&&m.safeParse(e).success?e:void 0}function E(e){return"string"==typeof e&&["EASY","MEDIUM","HARD"].includes(e)?e:void 0}function q(e,t){if(e.questionType)return d({...e,partId:void 0===e.partId?void 0:String(e.partId),partNo:e.partNo??1,questionNumber:e.questionNumber??t+1,points:e.points??1,addToQuestionBank:e.addToQuestionBank??!1,isExample:e.isExample??!1});const n=Array.isArray(e.options)?e.options:["A","B","C","D"];return d({questionType:e.type??"CHOOSE_THE_CORRECT_ANSWER",content:e.content??{question:String(e.question??e.text??`Question ${t+1}`),optionType:"text",options:n},correctAnswer:e.correctAnswer??{answer:I(e.answer??e.correctAnswerIndex??0)},points:e.points??1,explanation:e.explanation??"",questionNumber:e.questionNumber??t+1,partId:String(e.partId??1),partNo:e.partNo??1,sectionId:e.sectionId,questionSkillType:e.questionSkillType??e.skill??"READING",addToQuestionBank:e.addToQuestionBank??!1,isExample:e.isExample??!1})}function I(e){if("number"==typeof e)return e;if("string"==typeof e&&/^[A-E]$/i.test(e))return e.toUpperCase().charCodeAt(0)-65;const t=Number(e);return Number.isFinite(t)?t:0}function S(e){return Array.from({length:Number(e.count??1)},(t,n)=>w({questionType:String(e.type??"CHOOSE_THE_CORRECT_ANSWER"),questionNumber:n+1,partId:String(e.partId??1),partNo:Number(e.partNo??1),skill:String(e.skill??"READING")}))}function b(e){return(Array.isArray(e.parts)?e.parts.filter(c):[]).flatMap((e,t)=>{const n=O(e.startIndex,0),i=O(e.questionCount,O(e.endIndex,n)-n+1);return Array.from({length:Math.max(i,0)},(i,a)=>w({questionType:v(e.questionType,"CHOOSE_THE_CORRECT_ANSWER"),questionNumber:n+a+1,partId:String(e.id??t+1),partNo:t+1,sectionId:R(e.sectionId),skill:v(e.skill,"READING"),optionCount:O(e.optionCount,4),defaultValue:Array.isArray(e.defaultValue)?e.defaultValue:void 0}))})}function w(e){return d({questionType:e.questionType,content:C(e.questionType,e.questionNumber,e.optionCount,e.defaultValue),correctAnswer:N(e.questionType),points:1,explanation:"",questionNumber:e.questionNumber,partId:e.partId,partNo:e.partNo,sectionId:e.sectionId,questionSkillType:e.skill,addToQuestionBank:!1,isExample:!1})}function C(e,t,n=4,i){const a=(i?.length?i:Array.from({length:n},(e,t)=>String.fromCharCode(65+t))).map(String);return e.includes("FILL")||e.includes("WRITE")?{question:`Question ${t}`,text:""}:e.includes("SPEAK")?{prompt:`Question ${t}`}:e.includes("LISTEN")?{question:`Question ${t}`,audioUrl:"",optionType:"text",options:a}:{question:`Question ${t}`,optionType:"text",options:a}}function N(e){return e.includes("FILL")||e.includes("WRITE")?{answers:[""]}:e.includes("SPEAK")?{expectedAnswer:""}:{answer:0}}function v(e,t){return"string"==typeof e&&e?e:t}function O(e,t){const n=Number(e);return Number.isFinite(n)?n:t}function R(e){const t=Number(e);return Number.isFinite(t)?t:void 0}function k(e){const t=s(e);return Array.isArray(t)?t:t&&"object"==typeof t&&"data"in t&&Array.isArray(t.data)?t.data:[]}function P(e){const t=Array.isArray(e.parts)?e.parts:[];return d({id:e.id,title:e.title??e.name,subTitle:e.subTitle??e.sub_title??"",theme:e.theme,totalQuestions:e.totalQuestions??e.total_questions,totalParts:e.totalParts??e.total_parts??t.length,description:e.description??"",examLevel:e.examLevel??e.exam_level??e.level??"",type:e.type??null,parts:t})}
@@ -1,6 +1 @@
1
- import { registerExamTools } from './exams.js';
2
- import { registerResultTools } from './results.js';
3
- export function registerAllMcpTools(server) {
4
- registerExamTools(server);
5
- registerResultTools(server);
6
- }
1
+ import{registerExamTools as o}from"./exams.js";import{registerResultTools as r}from"./results.js";export function registerAllMcpTools(s){o(s),r(s)}
@@ -1,19 +1 @@
1
- import { z } from 'zod';
2
- import { mcpRequest } from '../http-client.js';
3
- import { buildToolResult, wrapHandler } from './_helpers.js';
4
- export function registerResultTools(server) {
5
- server.registerTool('exam_result_get', {
6
- title: 'Get exam result review',
7
- description: 'Fetch student exam result review by attemptId. Optional token supports public result review links.',
8
- inputSchema: {
9
- attemptId: z.string().min(1),
10
- token: z.string().min(1).optional(),
11
- includeAttempt: z.boolean().default(true),
12
- },
13
- }, wrapHandler('exam_result_get', async ({ attemptId, token, includeAttempt }) => {
14
- const query = token ? `?token=${encodeURIComponent(String(token))}` : '';
15
- const result = await mcpRequest({ method: 'GET', url: `/api/v1/student/results/${encodeURIComponent(String(attemptId))}${query}` });
16
- const attempt = includeAttempt === false ? null : await mcpRequest({ method: 'GET', url: `/api/v1/student/attempts/${encodeURIComponent(String(attemptId))}${query}` });
17
- return buildToolResult({ result, attempt });
18
- }));
19
- }
1
+ import{z as t}from"zod";import{mcpRequest as e}from"../http-client.js";import{buildToolResult as n,wrapHandler as o}from"./_helpers.js";export function registerResultTools(r){r.registerTool("exam_result_get",{title:"Get exam result review",description:"Fetch student exam result review by attemptId. Optional token supports public result review links.",inputSchema:{attemptId:t.string().min(1),token:t.string().min(1).optional(),includeAttempt:t.boolean().default(!0)}},o("exam_result_get",async({attemptId:t,token:o,includeAttempt:r})=>{const i=o?`?token=${encodeURIComponent(String(o))}`:"",s=await e({method:"GET",url:`/api/v1/student/results/${encodeURIComponent(String(t))}${i}`}),l=!1===r?null:await e({method:"GET",url:`/api/v1/student/attempts/${encodeURIComponent(String(t))}${i}`});return n({result:s,attempt:l})}))}