@magmamath/students-features 1.3.23-rc.2 → 1.3.23-rc.21

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 (306) hide show
  1. package/dist/commonjs/features/translation/constants.js +11 -0
  2. package/dist/commonjs/features/translation/constants.js.map +1 -0
  3. package/dist/commonjs/features/translation/helpers.js +60 -0
  4. package/dist/commonjs/features/translation/helpers.js.map +1 -0
  5. package/dist/commonjs/features/translation/index.js +105 -0
  6. package/dist/commonjs/features/translation/index.js.map +1 -0
  7. package/dist/commonjs/features/translation/model/TranslationModel.js +122 -0
  8. package/dist/commonjs/features/translation/model/TranslationModel.js.map +1 -0
  9. package/dist/commonjs/features/translation/model/TranslationPreprocessor.js +142 -0
  10. package/dist/commonjs/features/translation/model/TranslationPreprocessor.js.map +1 -0
  11. package/dist/commonjs/features/translation/model/TranslationService.js +112 -0
  12. package/dist/commonjs/features/translation/model/TranslationService.js.map +1 -0
  13. package/dist/commonjs/features/translation/preserve/wordsToPreserve.js +314 -0
  14. package/dist/commonjs/features/translation/preserve/wordsToPreserve.js.map +1 -0
  15. package/dist/commonjs/features/translation/providers/google/GoogleTranslationProvider.js +50 -0
  16. package/dist/commonjs/features/translation/providers/google/GoogleTranslationProvider.js.map +1 -0
  17. package/dist/commonjs/features/translation/providers/google/supportedLanguages.js +230 -0
  18. package/dist/commonjs/features/translation/providers/google/supportedLanguages.js.map +1 -0
  19. package/dist/commonjs/features/translation/providers/google.types.js +2 -0
  20. package/dist/commonjs/features/translation/providers/google.types.js.map +1 -0
  21. package/dist/commonjs/features/translation/providers/translation.interface.js +49 -0
  22. package/dist/commonjs/features/translation/providers/translation.interface.js.map +1 -0
  23. package/dist/commonjs/features/translation/types.js +2 -0
  24. package/dist/commonjs/features/translation/types.js.map +1 -0
  25. package/dist/commonjs/features/voice/index.js +4 -4
  26. package/dist/commonjs/features/voice/index.js.map +1 -1
  27. package/dist/commonjs/features/voice/playing/components/VoiceTranscriptContent.js +44 -0
  28. package/dist/commonjs/features/voice/playing/components/VoiceTranscriptContent.js.map +1 -0
  29. package/dist/commonjs/features/voice/playing/components/VoiceTranscription.js +107 -0
  30. package/dist/commonjs/features/voice/playing/components/VoiceTranscription.js.map +1 -0
  31. package/dist/commonjs/features/voice/playing/components/VoiceTranscriptionCollapsible.js +66 -0
  32. package/dist/commonjs/features/voice/playing/components/VoiceTranscriptionCollapsible.js.map +1 -0
  33. package/dist/commonjs/features/voice/playing/components/VoiceTranscriptionHeaderActions.js +58 -0
  34. package/dist/commonjs/features/voice/playing/components/VoiceTranscriptionHeaderActions.js.map +1 -0
  35. package/dist/commonjs/features/voice/playing/hooks/{useVoiceTranscriptionsDropdown.js → useDropdownAnimation.js} +8 -7
  36. package/dist/commonjs/features/voice/playing/hooks/useDropdownAnimation.js.map +1 -0
  37. package/dist/commonjs/features/voice/playing/hooks/useVoiceTranscriptionController.js +128 -0
  38. package/dist/commonjs/features/voice/playing/hooks/useVoiceTranscriptionController.js.map +1 -0
  39. package/dist/commonjs/features/voice/playing/model/{VoiceTranscriptionsDropdown.model.js → Dropdown.model.js} +4 -4
  40. package/dist/commonjs/features/voice/playing/model/Dropdown.model.js.map +1 -0
  41. package/dist/commonjs/features/voice/playing/model/PlaybackController.js +183 -0
  42. package/dist/commonjs/features/voice/playing/model/PlaybackController.js.map +1 -0
  43. package/dist/commonjs/features/voice/playing/model/Player.model.js +24 -1
  44. package/dist/commonjs/features/voice/playing/model/Player.model.js.map +1 -1
  45. package/dist/commonjs/features/voice/playing/model/{VoiceTranscriptionsCollection.js → TranscriptionsCollection.js} +4 -4
  46. package/dist/commonjs/features/voice/playing/model/TranscriptionsCollection.js.map +1 -0
  47. package/dist/commonjs/features/voice/playing/model/TranscriptionsDownloader.model.js +82 -0
  48. package/dist/commonjs/features/voice/playing/model/TranscriptionsDownloader.model.js.map +1 -0
  49. package/dist/commonjs/features/voice/playing/model/Translation.model.js +62 -0
  50. package/dist/commonjs/features/voice/playing/model/Translation.model.js.map +1 -0
  51. package/dist/commonjs/features/voice/playing/model/VoiceFileDownloader.model.js +2 -2
  52. package/dist/commonjs/features/voice/playing/model/VoiceFileDownloader.model.js.map +1 -1
  53. package/dist/commonjs/features/voice/playing/model/VoicePlayer.model.js +46 -172
  54. package/dist/commonjs/features/voice/playing/model/VoicePlayer.model.js.map +1 -1
  55. package/dist/commonjs/features/voice/types.js.map +1 -1
  56. package/dist/commonjs/index.js +12 -0
  57. package/dist/commonjs/index.js.map +1 -1
  58. package/dist/module/features/translation/constants.js +7 -0
  59. package/dist/module/features/translation/constants.js.map +1 -0
  60. package/dist/module/features/translation/helpers.js +53 -0
  61. package/dist/module/features/translation/helpers.js.map +1 -0
  62. package/dist/module/features/translation/index.js +12 -0
  63. package/dist/module/features/translation/index.js.map +1 -0
  64. package/dist/module/features/translation/model/TranslationModel.js +117 -0
  65. package/dist/module/features/translation/model/TranslationModel.js.map +1 -0
  66. package/dist/module/features/translation/model/TranslationPreprocessor.js +137 -0
  67. package/dist/module/features/translation/model/TranslationPreprocessor.js.map +1 -0
  68. package/dist/module/features/translation/model/TranslationService.js +107 -0
  69. package/dist/module/features/translation/model/TranslationService.js.map +1 -0
  70. package/dist/module/features/translation/preserve/wordsToPreserve.js +310 -0
  71. package/dist/module/features/translation/preserve/wordsToPreserve.js.map +1 -0
  72. package/dist/module/features/translation/providers/google/GoogleTranslationProvider.js +45 -0
  73. package/dist/module/features/translation/providers/google/GoogleTranslationProvider.js.map +1 -0
  74. package/dist/module/features/translation/providers/google/supportedLanguages.js +225 -0
  75. package/dist/module/features/translation/providers/google/supportedLanguages.js.map +1 -0
  76. package/dist/module/features/translation/providers/google.types.js +2 -0
  77. package/dist/module/features/translation/providers/google.types.js.map +1 -0
  78. package/dist/module/features/translation/providers/translation.interface.js +44 -0
  79. package/dist/module/features/translation/providers/translation.interface.js.map +1 -0
  80. package/dist/module/features/translation/types.js +2 -0
  81. package/dist/module/features/translation/types.js.map +1 -0
  82. package/dist/module/features/voice/index.js +1 -1
  83. package/dist/module/features/voice/index.js.map +1 -1
  84. package/dist/module/features/voice/playing/components/VoiceTranscriptContent.js +38 -0
  85. package/dist/module/features/voice/playing/components/VoiceTranscriptContent.js.map +1 -0
  86. package/dist/module/features/voice/playing/components/VoiceTranscription.js +101 -0
  87. package/dist/module/features/voice/playing/components/VoiceTranscription.js.map +1 -0
  88. package/dist/module/features/voice/playing/components/VoiceTranscriptionCollapsible.js +58 -0
  89. package/dist/module/features/voice/playing/components/VoiceTranscriptionCollapsible.js.map +1 -0
  90. package/dist/module/features/voice/playing/components/VoiceTranscriptionHeaderActions.js +52 -0
  91. package/dist/module/features/voice/playing/components/VoiceTranscriptionHeaderActions.js.map +1 -0
  92. package/dist/module/features/voice/playing/hooks/{useVoiceTranscriptionsDropdown.js → useDropdownAnimation.js} +6 -5
  93. package/dist/module/features/voice/playing/hooks/useDropdownAnimation.js.map +1 -0
  94. package/dist/module/features/voice/playing/hooks/useVoiceTranscriptionController.js +123 -0
  95. package/dist/module/features/voice/playing/hooks/useVoiceTranscriptionController.js.map +1 -0
  96. package/dist/module/features/voice/playing/model/{VoiceTranscriptionsDropdown.model.js → Dropdown.model.js} +2 -2
  97. package/dist/module/features/voice/playing/model/Dropdown.model.js.map +1 -0
  98. package/dist/module/features/voice/playing/model/PlaybackController.js +178 -0
  99. package/dist/module/features/voice/playing/model/PlaybackController.js.map +1 -0
  100. package/dist/module/features/voice/playing/model/Player.model.js +24 -1
  101. package/dist/module/features/voice/playing/model/Player.model.js.map +1 -1
  102. package/dist/module/features/voice/playing/model/{VoiceTranscriptionsCollection.js → TranscriptionsCollection.js} +2 -2
  103. package/dist/module/features/voice/playing/model/TranscriptionsCollection.js.map +1 -0
  104. package/dist/module/features/voice/playing/model/TranscriptionsDownloader.model.js +77 -0
  105. package/dist/module/features/voice/playing/model/TranscriptionsDownloader.model.js.map +1 -0
  106. package/dist/module/features/voice/playing/model/Translation.model.js +57 -0
  107. package/dist/module/features/voice/playing/model/Translation.model.js.map +1 -0
  108. package/dist/module/features/voice/playing/model/VoiceFileDownloader.model.js +2 -2
  109. package/dist/module/features/voice/playing/model/VoiceFileDownloader.model.js.map +1 -1
  110. package/dist/module/features/voice/playing/model/VoicePlayer.model.js +48 -174
  111. package/dist/module/features/voice/playing/model/VoicePlayer.model.js.map +1 -1
  112. package/dist/module/features/voice/types.js.map +1 -1
  113. package/dist/module/index.js +1 -0
  114. package/dist/module/index.js.map +1 -1
  115. package/dist/typescript/commonjs/features/translation/constants.d.ts +5 -0
  116. package/dist/typescript/commonjs/features/translation/constants.d.ts.map +1 -0
  117. package/dist/typescript/commonjs/features/translation/helpers.d.ts +9 -0
  118. package/dist/typescript/commonjs/features/translation/helpers.d.ts.map +1 -0
  119. package/dist/typescript/commonjs/features/translation/index.d.ts +12 -0
  120. package/dist/typescript/commonjs/features/translation/index.d.ts.map +1 -0
  121. package/dist/typescript/commonjs/features/translation/model/TranslationModel.d.ts +33 -0
  122. package/dist/typescript/commonjs/features/translation/model/TranslationModel.d.ts.map +1 -0
  123. package/dist/typescript/commonjs/features/translation/model/TranslationPreprocessor.d.ts +34 -0
  124. package/dist/typescript/commonjs/features/translation/model/TranslationPreprocessor.d.ts.map +1 -0
  125. package/dist/typescript/commonjs/features/translation/model/TranslationService.d.ts +40 -0
  126. package/dist/typescript/commonjs/features/translation/model/TranslationService.d.ts.map +1 -0
  127. package/dist/typescript/commonjs/features/translation/preserve/wordsToPreserve.d.ts +3 -0
  128. package/dist/typescript/commonjs/features/translation/preserve/wordsToPreserve.d.ts.map +1 -0
  129. package/dist/typescript/commonjs/features/translation/providers/google/GoogleTranslationProvider.d.ts +17 -0
  130. package/dist/typescript/commonjs/features/translation/providers/google/GoogleTranslationProvider.d.ts.map +1 -0
  131. package/dist/typescript/commonjs/features/translation/providers/google/supportedLanguages.d.ts +209 -0
  132. package/dist/typescript/commonjs/features/translation/providers/google/supportedLanguages.d.ts.map +1 -0
  133. package/dist/typescript/commonjs/features/translation/providers/google.types.d.ts +15 -0
  134. package/dist/typescript/commonjs/features/translation/providers/google.types.d.ts.map +1 -0
  135. package/dist/typescript/commonjs/features/translation/providers/translation.interface.d.ts +30 -0
  136. package/dist/typescript/commonjs/features/translation/providers/translation.interface.d.ts.map +1 -0
  137. package/dist/typescript/commonjs/features/translation/types.d.ts +27 -0
  138. package/dist/typescript/commonjs/features/translation/types.d.ts.map +1 -0
  139. package/dist/typescript/commonjs/features/voice/index.d.ts +2 -1
  140. package/dist/typescript/commonjs/features/voice/index.d.ts.map +1 -1
  141. package/dist/typescript/commonjs/features/voice/playing/components/VoiceTranscriptContent.d.ts +8 -0
  142. package/dist/typescript/commonjs/features/voice/playing/components/VoiceTranscriptContent.d.ts.map +1 -0
  143. package/dist/typescript/{module/features/voice/playing/components/VoiceTranscriptionsDropdown.d.ts → commonjs/features/voice/playing/components/VoiceTranscription.d.ts} +3 -4
  144. package/dist/typescript/commonjs/features/voice/playing/components/VoiceTranscription.d.ts.map +1 -0
  145. package/dist/typescript/commonjs/features/voice/playing/components/VoiceTranscriptionCollapsible.d.ts +10 -0
  146. package/dist/typescript/commonjs/features/voice/playing/components/VoiceTranscriptionCollapsible.d.ts.map +1 -0
  147. package/dist/typescript/commonjs/features/voice/playing/components/VoiceTranscriptionHeaderActions.d.ts +12 -0
  148. package/dist/typescript/commonjs/features/voice/playing/components/VoiceTranscriptionHeaderActions.d.ts.map +1 -0
  149. package/dist/typescript/{module/features/voice/playing/hooks/useVoiceTranscriptionsDropdown.d.ts → commonjs/features/voice/playing/hooks/useDropdownAnimation.d.ts} +3 -3
  150. package/dist/typescript/commonjs/features/voice/playing/hooks/useDropdownAnimation.d.ts.map +1 -0
  151. package/dist/typescript/commonjs/features/voice/playing/hooks/useVoiceTranscriptionController.d.ts +30 -0
  152. package/dist/typescript/commonjs/features/voice/playing/hooks/useVoiceTranscriptionController.d.ts.map +1 -0
  153. package/dist/typescript/{module/features/voice/playing/model/VoiceTranscriptionsDropdown.model.d.ts → commonjs/features/voice/playing/model/Dropdown.model.d.ts} +2 -2
  154. package/dist/typescript/commonjs/features/voice/playing/model/Dropdown.model.d.ts.map +1 -0
  155. package/dist/typescript/commonjs/features/voice/playing/model/PlaybackController.d.ts +28 -0
  156. package/dist/typescript/commonjs/features/voice/playing/model/PlaybackController.d.ts.map +1 -0
  157. package/dist/typescript/commonjs/features/voice/playing/model/Player.model.d.ts +3 -0
  158. package/dist/typescript/commonjs/features/voice/playing/model/Player.model.d.ts.map +1 -1
  159. package/dist/typescript/{module/features/voice/playing/model/VoiceTranscriptionsCollection.d.ts → commonjs/features/voice/playing/model/TranscriptionsCollection.d.ts} +2 -2
  160. package/dist/typescript/commonjs/features/voice/playing/model/TranscriptionsCollection.d.ts.map +1 -0
  161. package/dist/typescript/commonjs/features/voice/playing/model/TranscriptionsDownloader.model.d.ts +19 -0
  162. package/dist/typescript/commonjs/features/voice/playing/model/TranscriptionsDownloader.model.d.ts.map +1 -0
  163. package/dist/typescript/commonjs/features/voice/playing/model/Translation.model.d.ts +25 -0
  164. package/dist/typescript/commonjs/features/voice/playing/model/Translation.model.d.ts.map +1 -0
  165. package/dist/typescript/commonjs/features/voice/playing/model/VoiceFileDownloader.model.d.ts +2 -2
  166. package/dist/typescript/commonjs/features/voice/playing/model/VoiceFileDownloader.model.d.ts.map +1 -1
  167. package/dist/typescript/commonjs/features/voice/playing/model/VoicePlayer.model.d.ts +18 -18
  168. package/dist/typescript/commonjs/features/voice/playing/model/VoicePlayer.model.d.ts.map +1 -1
  169. package/dist/typescript/commonjs/features/voice/types.d.ts +5 -1
  170. package/dist/typescript/commonjs/features/voice/types.d.ts.map +1 -1
  171. package/dist/typescript/commonjs/index.d.ts +1 -0
  172. package/dist/typescript/commonjs/index.d.ts.map +1 -1
  173. package/dist/typescript/module/features/translation/constants.d.ts +5 -0
  174. package/dist/typescript/module/features/translation/constants.d.ts.map +1 -0
  175. package/dist/typescript/module/features/translation/helpers.d.ts +9 -0
  176. package/dist/typescript/module/features/translation/helpers.d.ts.map +1 -0
  177. package/dist/typescript/module/features/translation/index.d.ts +12 -0
  178. package/dist/typescript/module/features/translation/index.d.ts.map +1 -0
  179. package/dist/typescript/module/features/translation/model/TranslationModel.d.ts +33 -0
  180. package/dist/typescript/module/features/translation/model/TranslationModel.d.ts.map +1 -0
  181. package/dist/typescript/module/features/translation/model/TranslationPreprocessor.d.ts +34 -0
  182. package/dist/typescript/module/features/translation/model/TranslationPreprocessor.d.ts.map +1 -0
  183. package/dist/typescript/module/features/translation/model/TranslationService.d.ts +40 -0
  184. package/dist/typescript/module/features/translation/model/TranslationService.d.ts.map +1 -0
  185. package/dist/typescript/module/features/translation/preserve/wordsToPreserve.d.ts +3 -0
  186. package/dist/typescript/module/features/translation/preserve/wordsToPreserve.d.ts.map +1 -0
  187. package/dist/typescript/module/features/translation/providers/google/GoogleTranslationProvider.d.ts +17 -0
  188. package/dist/typescript/module/features/translation/providers/google/GoogleTranslationProvider.d.ts.map +1 -0
  189. package/dist/typescript/module/features/translation/providers/google/supportedLanguages.d.ts +209 -0
  190. package/dist/typescript/module/features/translation/providers/google/supportedLanguages.d.ts.map +1 -0
  191. package/dist/typescript/module/features/translation/providers/google.types.d.ts +15 -0
  192. package/dist/typescript/module/features/translation/providers/google.types.d.ts.map +1 -0
  193. package/dist/typescript/module/features/translation/providers/translation.interface.d.ts +30 -0
  194. package/dist/typescript/module/features/translation/providers/translation.interface.d.ts.map +1 -0
  195. package/dist/typescript/module/features/translation/types.d.ts +27 -0
  196. package/dist/typescript/module/features/translation/types.d.ts.map +1 -0
  197. package/dist/typescript/module/features/voice/index.d.ts +2 -1
  198. package/dist/typescript/module/features/voice/index.d.ts.map +1 -1
  199. package/dist/typescript/module/features/voice/playing/components/VoiceTranscriptContent.d.ts +8 -0
  200. package/dist/typescript/module/features/voice/playing/components/VoiceTranscriptContent.d.ts.map +1 -0
  201. package/dist/typescript/{commonjs/features/voice/playing/components/VoiceTranscriptionsDropdown.d.ts → module/features/voice/playing/components/VoiceTranscription.d.ts} +3 -4
  202. package/dist/typescript/module/features/voice/playing/components/VoiceTranscription.d.ts.map +1 -0
  203. package/dist/typescript/module/features/voice/playing/components/VoiceTranscriptionCollapsible.d.ts +10 -0
  204. package/dist/typescript/module/features/voice/playing/components/VoiceTranscriptionCollapsible.d.ts.map +1 -0
  205. package/dist/typescript/module/features/voice/playing/components/VoiceTranscriptionHeaderActions.d.ts +12 -0
  206. package/dist/typescript/module/features/voice/playing/components/VoiceTranscriptionHeaderActions.d.ts.map +1 -0
  207. package/dist/typescript/{commonjs/features/voice/playing/hooks/useVoiceTranscriptionsDropdown.d.ts → module/features/voice/playing/hooks/useDropdownAnimation.d.ts} +3 -3
  208. package/dist/typescript/module/features/voice/playing/hooks/useDropdownAnimation.d.ts.map +1 -0
  209. package/dist/typescript/module/features/voice/playing/hooks/useVoiceTranscriptionController.d.ts +30 -0
  210. package/dist/typescript/module/features/voice/playing/hooks/useVoiceTranscriptionController.d.ts.map +1 -0
  211. package/dist/typescript/{commonjs/features/voice/playing/model/VoiceTranscriptionsDropdown.model.d.ts → module/features/voice/playing/model/Dropdown.model.d.ts} +2 -2
  212. package/dist/typescript/module/features/voice/playing/model/Dropdown.model.d.ts.map +1 -0
  213. package/dist/typescript/module/features/voice/playing/model/PlaybackController.d.ts +28 -0
  214. package/dist/typescript/module/features/voice/playing/model/PlaybackController.d.ts.map +1 -0
  215. package/dist/typescript/module/features/voice/playing/model/Player.model.d.ts +3 -0
  216. package/dist/typescript/module/features/voice/playing/model/Player.model.d.ts.map +1 -1
  217. package/dist/typescript/{commonjs/features/voice/playing/model/VoiceTranscriptionsCollection.d.ts → module/features/voice/playing/model/TranscriptionsCollection.d.ts} +2 -2
  218. package/dist/typescript/module/features/voice/playing/model/TranscriptionsCollection.d.ts.map +1 -0
  219. package/dist/typescript/module/features/voice/playing/model/TranscriptionsDownloader.model.d.ts +19 -0
  220. package/dist/typescript/module/features/voice/playing/model/TranscriptionsDownloader.model.d.ts.map +1 -0
  221. package/dist/typescript/module/features/voice/playing/model/Translation.model.d.ts +25 -0
  222. package/dist/typescript/module/features/voice/playing/model/Translation.model.d.ts.map +1 -0
  223. package/dist/typescript/module/features/voice/playing/model/VoiceFileDownloader.model.d.ts +2 -2
  224. package/dist/typescript/module/features/voice/playing/model/VoiceFileDownloader.model.d.ts.map +1 -1
  225. package/dist/typescript/module/features/voice/playing/model/VoicePlayer.model.d.ts +18 -18
  226. package/dist/typescript/module/features/voice/playing/model/VoicePlayer.model.d.ts.map +1 -1
  227. package/dist/typescript/module/features/voice/types.d.ts +5 -1
  228. package/dist/typescript/module/features/voice/types.d.ts.map +1 -1
  229. package/dist/typescript/module/index.d.ts +1 -0
  230. package/dist/typescript/module/index.d.ts.map +1 -1
  231. package/package.json +28 -1
  232. package/src/features/translation/constants.ts +4 -0
  233. package/src/features/translation/helpers.ts +63 -0
  234. package/src/features/translation/index.ts +30 -0
  235. package/src/features/translation/model/TranslationModel.ts +174 -0
  236. package/src/features/translation/model/TranslationPreprocessor.ts +171 -0
  237. package/src/features/translation/model/TranslationService.ts +148 -0
  238. package/src/features/translation/preserve/wordsToPreserve.ts +87 -0
  239. package/src/features/translation/providers/google/GoogleTranslationProvider.ts +50 -0
  240. package/src/features/translation/providers/google/supportedLanguages.ts +229 -0
  241. package/src/features/translation/providers/google.types.ts +16 -0
  242. package/src/features/translation/providers/translation.interface.ts +63 -0
  243. package/src/features/translation/types.ts +30 -0
  244. package/src/features/voice/index.ts +2 -1
  245. package/src/features/voice/playing/components/VoiceTranscriptContent.tsx +37 -0
  246. package/src/features/voice/playing/components/VoiceTranscription.tsx +117 -0
  247. package/src/features/voice/playing/components/VoiceTranscriptionCollapsible.tsx +59 -0
  248. package/src/features/voice/playing/components/VoiceTranscriptionHeaderActions.tsx +61 -0
  249. package/src/features/voice/playing/hooks/{useVoiceTranscriptionsDropdown.ts → useDropdownAnimation.ts} +5 -5
  250. package/src/features/voice/playing/hooks/useVoiceTranscriptionController.ts +171 -0
  251. package/src/features/voice/playing/model/{VoiceTranscriptionsDropdown.model.ts → Dropdown.model.ts} +2 -2
  252. package/src/features/voice/playing/model/PlaybackController.ts +213 -0
  253. package/src/features/voice/playing/model/Player.model.ts +26 -1
  254. package/src/features/voice/playing/model/{VoiceTranscriptionsCollection.ts → TranscriptionsCollection.ts} +1 -1
  255. package/src/features/voice/playing/model/TranscriptionsDownloader.model.ts +88 -0
  256. package/src/features/voice/playing/model/Translation.model.ts +75 -0
  257. package/src/features/voice/playing/model/VoiceFileDownloader.model.ts +2 -2
  258. package/src/features/voice/playing/model/VoicePlayer.model.ts +57 -194
  259. package/src/features/voice/types.ts +10 -0
  260. package/src/index.ts +1 -0
  261. package/dist/commonjs/features/voice/playing/components/VoiceTranscriptionsDropdown.js +0 -251
  262. package/dist/commonjs/features/voice/playing/components/VoiceTranscriptionsDropdown.js.map +0 -1
  263. package/dist/commonjs/features/voice/playing/components/VoiceTranscriptionsDropdownItem.js +0 -109
  264. package/dist/commonjs/features/voice/playing/components/VoiceTranscriptionsDropdownItem.js.map +0 -1
  265. package/dist/commonjs/features/voice/playing/hooks/useVoiceTranscriptionsDropdown.js.map +0 -1
  266. package/dist/commonjs/features/voice/playing/hooks/useVoiceTranscriptionsDropdownItemAnimation.js +0 -34
  267. package/dist/commonjs/features/voice/playing/hooks/useVoiceTranscriptionsDropdownItemAnimation.js.map +0 -1
  268. package/dist/commonjs/features/voice/playing/model/TranscriptionsDownloaderModel.js +0 -66
  269. package/dist/commonjs/features/voice/playing/model/TranscriptionsDownloaderModel.js.map +0 -1
  270. package/dist/commonjs/features/voice/playing/model/VoiceTranscriptionsCollection.js.map +0 -1
  271. package/dist/commonjs/features/voice/playing/model/VoiceTranscriptionsDropdown.model.js.map +0 -1
  272. package/dist/module/features/voice/playing/components/VoiceTranscriptionsDropdown.js +0 -244
  273. package/dist/module/features/voice/playing/components/VoiceTranscriptionsDropdown.js.map +0 -1
  274. package/dist/module/features/voice/playing/components/VoiceTranscriptionsDropdownItem.js +0 -103
  275. package/dist/module/features/voice/playing/components/VoiceTranscriptionsDropdownItem.js.map +0 -1
  276. package/dist/module/features/voice/playing/hooks/useVoiceTranscriptionsDropdown.js.map +0 -1
  277. package/dist/module/features/voice/playing/hooks/useVoiceTranscriptionsDropdownItemAnimation.js +0 -29
  278. package/dist/module/features/voice/playing/hooks/useVoiceTranscriptionsDropdownItemAnimation.js.map +0 -1
  279. package/dist/module/features/voice/playing/model/TranscriptionsDownloaderModel.js +0 -61
  280. package/dist/module/features/voice/playing/model/TranscriptionsDownloaderModel.js.map +0 -1
  281. package/dist/module/features/voice/playing/model/VoiceTranscriptionsCollection.js.map +0 -1
  282. package/dist/module/features/voice/playing/model/VoiceTranscriptionsDropdown.model.js.map +0 -1
  283. package/dist/typescript/commonjs/features/voice/playing/components/VoiceTranscriptionsDropdown.d.ts.map +0 -1
  284. package/dist/typescript/commonjs/features/voice/playing/components/VoiceTranscriptionsDropdownItem.d.ts +0 -12
  285. package/dist/typescript/commonjs/features/voice/playing/components/VoiceTranscriptionsDropdownItem.d.ts.map +0 -1
  286. package/dist/typescript/commonjs/features/voice/playing/hooks/useVoiceTranscriptionsDropdown.d.ts.map +0 -1
  287. package/dist/typescript/commonjs/features/voice/playing/hooks/useVoiceTranscriptionsDropdownItemAnimation.d.ts +0 -11
  288. package/dist/typescript/commonjs/features/voice/playing/hooks/useVoiceTranscriptionsDropdownItemAnimation.d.ts.map +0 -1
  289. package/dist/typescript/commonjs/features/voice/playing/model/TranscriptionsDownloaderModel.d.ts +0 -15
  290. package/dist/typescript/commonjs/features/voice/playing/model/TranscriptionsDownloaderModel.d.ts.map +0 -1
  291. package/dist/typescript/commonjs/features/voice/playing/model/VoiceTranscriptionsCollection.d.ts.map +0 -1
  292. package/dist/typescript/commonjs/features/voice/playing/model/VoiceTranscriptionsDropdown.model.d.ts.map +0 -1
  293. package/dist/typescript/module/features/voice/playing/components/VoiceTranscriptionsDropdown.d.ts.map +0 -1
  294. package/dist/typescript/module/features/voice/playing/components/VoiceTranscriptionsDropdownItem.d.ts +0 -12
  295. package/dist/typescript/module/features/voice/playing/components/VoiceTranscriptionsDropdownItem.d.ts.map +0 -1
  296. package/dist/typescript/module/features/voice/playing/hooks/useVoiceTranscriptionsDropdown.d.ts.map +0 -1
  297. package/dist/typescript/module/features/voice/playing/hooks/useVoiceTranscriptionsDropdownItemAnimation.d.ts +0 -11
  298. package/dist/typescript/module/features/voice/playing/hooks/useVoiceTranscriptionsDropdownItemAnimation.d.ts.map +0 -1
  299. package/dist/typescript/module/features/voice/playing/model/TranscriptionsDownloaderModel.d.ts +0 -15
  300. package/dist/typescript/module/features/voice/playing/model/TranscriptionsDownloaderModel.d.ts.map +0 -1
  301. package/dist/typescript/module/features/voice/playing/model/VoiceTranscriptionsCollection.d.ts.map +0 -1
  302. package/dist/typescript/module/features/voice/playing/model/VoiceTranscriptionsDropdown.model.d.ts.map +0 -1
  303. package/src/features/voice/playing/components/VoiceTranscriptionsDropdown.tsx +0 -302
  304. package/src/features/voice/playing/components/VoiceTranscriptionsDropdownItem.tsx +0 -128
  305. package/src/features/voice/playing/hooks/useVoiceTranscriptionsDropdownItemAnimation.ts +0 -40
  306. package/src/features/voice/playing/model/TranscriptionsDownloaderModel.ts +0 -80
@@ -0,0 +1,171 @@
1
+ import { useUnit } from 'effector-react'
2
+ import { useCallback, useEffect, useMemo, useRef } from 'react'
3
+ import { useTranslation } from 'react-i18next'
4
+ import { NO_AUDIO_BE_MESSAGE } from '../../constants'
5
+ import { getActiveAttempt } from '../../helpers'
6
+ import { AnswerAudio } from '../../types'
7
+ import { resolveGoogleLanguageCode } from '../../../translation/providers/google/supportedLanguages'
8
+ import { useText } from '../../../../i18n/i18n'
9
+ import { VoicePlayerModel } from '../model'
10
+ import { PlayerState } from '../model/Player.model'
11
+
12
+ type Params = {
13
+ model: VoicePlayerModel
14
+ answers: AnswerAudio[]
15
+ attemptOffset: number
16
+ attemptsCount: number
17
+ problemId: string
18
+ }
19
+
20
+ export type VoiceTranscriptionController = ReturnType<typeof useVoiceTranscriptionController>
21
+
22
+ export const useVoiceTranscriptionController = ({
23
+ model,
24
+ answers,
25
+ attemptOffset,
26
+ attemptsCount,
27
+ problemId,
28
+ }: Params) => {
29
+ const t = useText()
30
+ const { i18n } = useTranslation()
31
+
32
+ const [playerState, isLoadingTranscripts, isTranslationActive, translations, isTranslating] =
33
+ useUnit([
34
+ model.$playerState,
35
+ model.loader.loadTranscriptForAttempt.pending,
36
+ model.translation.$isActive,
37
+ model.translation.$translations,
38
+ model.translation.translate.pending,
39
+ ])
40
+
41
+ const activeAttempt = getActiveAttempt(attemptOffset, attemptsCount)
42
+
43
+ const answersKey = useMemo(
44
+ () => answers.map((a) => `${a._id}:${a.audioFileId ?? ''}`).join(','),
45
+ [answers],
46
+ )
47
+
48
+ const activeAudioFileId =
49
+ activeAttempt !== null ? answers?.[activeAttempt - 1]?.audioFileId : undefined
50
+ const hasActiveAudio = activeAudioFileId !== undefined
51
+ const anyAttemptHasAudio = useMemo(() => answers.some((a) => !!a.audioFileId), [answers])
52
+
53
+ const cachedEntry = activeAudioFileId
54
+ ? model.loader.getCachedTranscript(activeAudioFileId)
55
+ : undefined
56
+ const activeTranscript = cachedEntry?.text ?? null
57
+ const hasTranscriptText = !!activeTranscript && activeTranscript !== NO_AUDIO_BE_MESSAGE
58
+ const isActiveTranscriptLoading = isLoadingTranscripts && hasActiveAudio && !activeTranscript
59
+
60
+ const isSourceSameAsTarget =
61
+ !!cachedEntry?.language &&
62
+ resolveGoogleLanguageCode(cachedEntry.language) === resolveGoogleLanguageCode(i18n.language)
63
+
64
+ const canTranslate = hasTranscriptText && !isSourceSameAsTarget
65
+
66
+ const cachedTranslation = activeAttempt !== null ? translations[activeAttempt] : undefined
67
+ const showTranslation = isTranslationActive && canTranslate
68
+ const isTranslationLoading = showTranslation && !cachedTranslation && isTranslating
69
+
70
+ const displayText = showTranslation
71
+ ? (cachedTranslation ?? activeTranscript!)
72
+ : hasTranscriptText
73
+ ? activeTranscript!
74
+ : t('voice.transcriptNotAvailable')
75
+
76
+ // Lifecycle effects
77
+ const answersRef = useRef(answers)
78
+ answersRef.current = answers
79
+
80
+ useEffect(() => {
81
+ model.reinitializePlayer()
82
+ return () => {
83
+ model.stopAudio()
84
+ model.dropdown.setExpanded(false)
85
+ }
86
+ }, [model])
87
+
88
+ useEffect(() => {
89
+ model.dropdown.setExpanded(false)
90
+ model.resetPlayer()
91
+ }, [problemId])
92
+
93
+ useEffect(() => {
94
+ if (answersRef.current.some((a) => a.audioFileId)) {
95
+ model.initializeWithAudios(answersRef.current)
96
+ }
97
+ }, [model, answersKey])
98
+
99
+ useEffect(() => {
100
+ model.stopAudio()
101
+ model.setCurrentAttempt(activeAttempt)
102
+ if (activeAttempt !== null) {
103
+ model.loader.loadTranscriptForAttempt(activeAttempt)
104
+ }
105
+ }, [model, activeAttempt, answersKey])
106
+
107
+ // Auto-activate translation once per (problemId, activeAttempt) when translatable
108
+ const autoActivatedAttempts = useRef(new Set<number>())
109
+ useEffect(() => {
110
+ autoActivatedAttempts.current.clear()
111
+ }, [problemId])
112
+ useEffect(() => {
113
+ if (
114
+ canTranslate &&
115
+ activeAttempt !== null &&
116
+ !autoActivatedAttempts.current.has(activeAttempt)
117
+ ) {
118
+ autoActivatedAttempts.current.add(activeAttempt)
119
+ model.translation.setActive(true)
120
+ }
121
+ }, [canTranslate, activeAttempt, model])
122
+
123
+ // Trigger translate when we should show translation but don't have it cached
124
+ useEffect(() => {
125
+ if (showTranslation && activeAttempt !== null && !cachedTranslation && !isTranslating) {
126
+ model.translation.translate({
127
+ attemptNumber: activeAttempt,
128
+ targetLanguage: i18n.language,
129
+ })
130
+ }
131
+ }, [showTranslation, activeAttempt, cachedTranslation, isTranslating, i18n.language, model])
132
+
133
+ // Auto-open dropdown when a translation successfully completes
134
+ useEffect(() => {
135
+ return model.translation.translate.doneData.watch((result) => {
136
+ if (result) {
137
+ model.dropdown.setExpanded(true)
138
+ }
139
+ })
140
+ }, [model])
141
+
142
+ const onPlayPress = useCallback(() => {
143
+ if (activeAttempt) {
144
+ model.translation.setActive(false)
145
+ model.togglePlayPause(activeAttempt)
146
+ }
147
+ }, [model, activeAttempt])
148
+
149
+ const onTranslationToggle = useCallback(() => {
150
+ model.translation.toggle()
151
+ model.dropdown.setExpanded(true)
152
+ }, [model])
153
+
154
+ return {
155
+ display: {
156
+ text: displayText,
157
+ isLoading: isActiveTranscriptLoading || isTranslationLoading,
158
+ },
159
+ flags: {
160
+ playerState: playerState as PlayerState,
161
+ hasActiveAudio,
162
+ anyAttemptHasAudio,
163
+ canTranslate,
164
+ isTranslationActive,
165
+ },
166
+ actions: {
167
+ onPlayPress,
168
+ onTranslationToggle,
169
+ },
170
+ }
171
+ }
@@ -1,6 +1,6 @@
1
- import { createEvent, createStore, restore } from 'effector'
1
+ import { createEvent, restore } from 'effector'
2
2
 
3
- export class VoiceTranscriptionsDropdownModel {
3
+ export class DropdownModel {
4
4
  public readonly toggleExpand = createEvent()
5
5
  public readonly setExpanded = createEvent<boolean>()
6
6
  public readonly reset = createEvent()
@@ -0,0 +1,213 @@
1
+ import { createEffect } from 'effector'
2
+ import { PlayerModel, PlayerState } from './Player.model'
3
+ import { TranscriptionsCollection } from './TranscriptionsCollection'
4
+ import { VoiceFileDownloaderModel } from './VoiceFileDownloader.model'
5
+
6
+ type PlaybackControllerDeps = {
7
+ collection: TranscriptionsCollection
8
+ player: PlayerModel
9
+ downloader: VoiceFileDownloaderModel
10
+ }
11
+
12
+ export class PlaybackController {
13
+ private readonly collection: TranscriptionsCollection
14
+ private readonly player: PlayerModel
15
+ private readonly downloader: VoiceFileDownloaderModel
16
+ private currentPlayingAttempt: number | null = null
17
+ private shouldPreventPlayback = false
18
+
19
+ constructor(deps: PlaybackControllerDeps) {
20
+ this.collection = deps.collection
21
+ this.player = deps.player
22
+ this.downloader = deps.downloader
23
+ this.player.playbackFinished.watch(() => this.clearCurrentAttempt())
24
+ }
25
+
26
+ public readonly togglePlayPause = (attemptNumber: number) => {
27
+ const playerState = this.player.$playerState.getState()
28
+ if (this.currentPlayingAttempt === attemptNumber && playerState === PlayerState.PLAYING) {
29
+ this.player.pause()
30
+ } else {
31
+ this.handlePlayAttempt(attemptNumber)
32
+ }
33
+ }
34
+
35
+ public readonly preloadAudio = createEffect(async (attemptNumber: number) => {
36
+ const item = this.collection.get(attemptNumber)
37
+ if (!item) return
38
+ if (this.collection.hasAudio(attemptNumber)) {
39
+ this.primePlayerIfIdle(attemptNumber)
40
+ return
41
+ }
42
+ if (item.audioDownloadPromise) {
43
+ try {
44
+ await item.audioDownloadPromise
45
+ } catch {}
46
+ this.primePlayerIfIdle(attemptNumber)
47
+ return
48
+ }
49
+ try {
50
+ await this.downloadAudio(attemptNumber)
51
+ this.primePlayerIfIdle(attemptNumber)
52
+ } catch (error) {
53
+ if (error instanceof Error && error.name === 'AbortError') return
54
+ console.error('Audio preload error:', error)
55
+ }
56
+ })
57
+
58
+ private primePlayerIfIdle(attemptNumber: number) {
59
+ if (this.player.$playerState.getState() !== PlayerState.IDLE) return
60
+ if (this.currentPlayingAttempt !== null) return
61
+ const audioUri = this.collection.get(attemptNumber)?.audioUri
62
+ if (!audioUri) return
63
+ this.player.prime(audioUri)
64
+ }
65
+
66
+ public halt() {
67
+ this.shouldPreventPlayback = true
68
+ this.abortAllDownloads()
69
+ this.player.stop()
70
+ this.clearCurrentAttempt()
71
+ }
72
+
73
+ public releaseResources() {
74
+ this.collection.getAll().forEach((item) => {
75
+ if (item.controller) {
76
+ this.downloader.abortDownload(item.controller)
77
+ }
78
+ if (item.audioUri?.startsWith('blob:')) {
79
+ try {
80
+ URL.revokeObjectURL(item.audioUri)
81
+ } catch (e) {}
82
+ }
83
+ })
84
+ }
85
+
86
+ private clearCurrentAttempt() {
87
+ this.currentPlayingAttempt = null
88
+ }
89
+
90
+ private abortAllDownloads() {
91
+ this.collection.getAll().forEach((item) => {
92
+ if (item.controller && item.audioLoading) {
93
+ this.downloader.abortDownload(item.controller)
94
+ this.collection.update(item.attemptNumber, {
95
+ audioLoading: false,
96
+ controller: undefined,
97
+ audioDownloadPromise: undefined,
98
+ })
99
+ }
100
+ })
101
+ }
102
+
103
+ private async downloadAudio(attemptNumber: number): Promise<void> {
104
+ const item = this.collection.get(attemptNumber)
105
+ if (!item) return
106
+
107
+ const { collectionItem, downloadPromise } = await this.downloader.download({
108
+ audioFileId: item.audioFileId,
109
+ attemptNumber,
110
+ })
111
+ this.collection.update(attemptNumber, collectionItem)
112
+
113
+ try {
114
+ const result = await downloadPromise
115
+ this.collection.update(attemptNumber, {
116
+ audioUri: result.uri,
117
+ audioLoading: false,
118
+ audioError: undefined,
119
+ controller: undefined,
120
+ audioDownloadPromise: undefined,
121
+ })
122
+ } catch (error) {
123
+ const isAbort = error instanceof Error && error.name === 'AbortError'
124
+ this.collection.update(attemptNumber, {
125
+ audioLoading: false,
126
+ audioError: isAbort
127
+ ? undefined
128
+ : error instanceof Error
129
+ ? error.message
130
+ : 'Failed to download audio',
131
+ controller: undefined,
132
+ audioDownloadPromise: undefined,
133
+ })
134
+ throw error
135
+ }
136
+ }
137
+
138
+ private readonly handlePlayAttempt = createEffect(async (attemptNumber: number) => {
139
+ this.shouldPreventPlayback = false
140
+ const item = this.collection.get(attemptNumber)
141
+ if (!item) return
142
+
143
+ if (this.currentPlayingAttempt !== null && this.currentPlayingAttempt !== attemptNumber) {
144
+ this.abortAllDownloads()
145
+ }
146
+
147
+ if (this.collection.hasAudio(attemptNumber)) {
148
+ this.playAudioFx(attemptNumber)
149
+ return
150
+ }
151
+
152
+ try {
153
+ this.player.setPlayerState(PlayerState.LOADING)
154
+
155
+ if (item.audioDownloadPromise) {
156
+ await item.audioDownloadPromise
157
+ } else {
158
+ await this.downloadAudio(attemptNumber)
159
+ }
160
+
161
+ if (this.shouldPreventPlayback) {
162
+ this.player.setPlayerState(PlayerState.IDLE)
163
+ return
164
+ }
165
+
166
+ this.player.setPlayerState(PlayerState.IDLE)
167
+ this.playAudioFx(attemptNumber)
168
+ } catch (error) {
169
+ if (error instanceof Error && error.name === 'AbortError') {
170
+ this.player.setPlayerState(PlayerState.IDLE)
171
+ return
172
+ }
173
+ console.error('Audio download error:', error)
174
+ this.player.setPlayerState(PlayerState.IDLE)
175
+ }
176
+ })
177
+
178
+ private readonly playAudioFx = createEffect(async (attemptNumber: number) => {
179
+ if (this.shouldPreventPlayback) return
180
+
181
+ const item = this.collection.get(attemptNumber)
182
+ if (!item?.audioUri) {
183
+ console.warn('No audio URI available for attempt:', attemptNumber)
184
+ return
185
+ }
186
+
187
+ try {
188
+ if (this.currentPlayingAttempt !== null && this.currentPlayingAttempt !== attemptNumber) {
189
+ this.player.player?.remove()
190
+ }
191
+
192
+ if (this.currentPlayingAttempt === attemptNumber && this.player.player?.playing) {
193
+ this.player.pause()
194
+ return
195
+ }
196
+
197
+ if (this.currentPlayingAttempt === attemptNumber && !this.player.player?.playing) {
198
+ this.player.resume()
199
+ return
200
+ }
201
+
202
+ this.currentPlayingAttempt = attemptNumber
203
+ await this.player.play(item.audioUri)
204
+ } catch (error) {
205
+ console.error('Error playing audio:', error)
206
+ this.player.setPlayerState(PlayerState.IDLE)
207
+
208
+ this.collection.update(attemptNumber, {
209
+ audioError: error instanceof Error ? error.message : 'Failed to play audio',
210
+ })
211
+ }
212
+ })
213
+ }
@@ -11,6 +11,7 @@ export const enum PlayerState {
11
11
  export class PlayerModel {
12
12
  public player: AudioPlayer | null = createAudioPlayer()
13
13
  private stopped = false
14
+ private primedUri: string | null = null
14
15
 
15
16
  public readonly reset = createEvent()
16
17
  public readonly setPlayerState = createEvent<PlayerState>()
@@ -39,12 +40,32 @@ export class PlayerModel {
39
40
  this.stopped = true
40
41
  if (this.player) {
41
42
  this.player.replace('')
43
+ this.primedUri = null
42
44
  this.setPlayerState(PlayerState.IDLE)
43
45
  } else {
44
46
  console.warn('Cannot stop: audio player not initialized')
45
47
  }
46
48
  })
47
49
 
50
+ public readonly prime = createEffect((uri: string) => {
51
+ if (!this.player) return
52
+ if (this.primedUri === uri) return
53
+ this.primedUri = uri
54
+ this.player.replace(uri)
55
+ })
56
+
57
+ public readonly seek = createEffect(async (seconds: number) => {
58
+ if (!this.player) {
59
+ console.warn('Cannot seek: audio player not initialized')
60
+ return
61
+ }
62
+ if (!this.player.isLoaded) {
63
+ console.warn('Cannot seek: audio player not loaded')
64
+ return
65
+ }
66
+ await this.player.seekTo(seconds)
67
+ })
68
+
48
69
  public readonly resume = createEffect(() => {
49
70
  if (this.player) {
50
71
  this.player.play()
@@ -61,7 +82,10 @@ export class PlayerModel {
61
82
  }
62
83
 
63
84
  this.stopped = false
64
- this.player.replace(uri)
85
+ if (this.primedUri !== uri) {
86
+ this.player.replace(uri)
87
+ this.primedUri = uri
88
+ }
65
89
 
66
90
  const waitForLoad = () => {
67
91
  if (this.stopped) return
@@ -99,6 +123,7 @@ export class PlayerModel {
99
123
  this.player.release()
100
124
  this.player = null
101
125
  }
126
+ this.primedUri = null
102
127
  this.reset()
103
128
  }
104
129
  }
@@ -1,6 +1,6 @@
1
1
  import { VoiceTranscriptionItem } from '../../types'
2
2
 
3
- export class VoiceTranscriptionsCollection {
3
+ export class TranscriptionsCollection {
4
4
  public readonly collection: Map<number, VoiceTranscriptionItem> = new Map()
5
5
 
6
6
  public add(attemptNumber: number, item: VoiceTranscriptionItem) {
@@ -0,0 +1,88 @@
1
+ import { createEffect } from 'effector'
2
+ import { TranscriptionsCollection } from './TranscriptionsCollection'
3
+ import { VoicePlayerApi } from '../../types'
4
+ import {
5
+ NO_AUDIO_BE_MESSAGE,
6
+ TRANSCRIPT_MAX_RETRIES,
7
+ TRANSCRIPT_RETRY_INTERVAL_MS,
8
+ } from '../../constants'
9
+
10
+ type TranscriptResult = { text: string; language?: string }
11
+
12
+ export class TranscriptionsDownloaderModel {
13
+ private readonly collection: TranscriptionsCollection
14
+ private readonly api: VoicePlayerApi
15
+ private readonly inflightFetches = new Map<string, Promise<TranscriptResult>>()
16
+ private readonly transcriptCache = new Map<string, TranscriptResult>()
17
+
18
+ constructor(collection: TranscriptionsCollection, api: VoicePlayerApi) {
19
+ this.collection = collection
20
+ this.api = api
21
+ }
22
+
23
+ private fetchTranscriptWithRetry(audioFileId: string): Promise<TranscriptResult> {
24
+ const cached = this.transcriptCache.get(audioFileId)
25
+ if (cached) return Promise.resolve(cached)
26
+
27
+ const existing = this.inflightFetches.get(audioFileId)
28
+ if (existing) return existing
29
+
30
+ const promise = this.runFetchWithRetry(audioFileId)
31
+ .then((result) => {
32
+ this.transcriptCache.set(audioFileId, result)
33
+ return result
34
+ })
35
+ .finally(() => {
36
+ this.inflightFetches.delete(audioFileId)
37
+ })
38
+ this.inflightFetches.set(audioFileId, promise)
39
+ return promise
40
+ }
41
+
42
+ private async runFetchWithRetry(audioFileId: string): Promise<TranscriptResult> {
43
+ for (let attempt = 0; attempt < TRANSCRIPT_MAX_RETRIES; attempt++) {
44
+ const response = await this.api.getAudioFileTranscript(audioFileId)
45
+
46
+ if (response.status === 'completed') {
47
+ return { text: response.text, language: response.language }
48
+ }
49
+
50
+ if (response.status === 'failed') {
51
+ return { text: NO_AUDIO_BE_MESSAGE }
52
+ }
53
+
54
+ if (attempt < TRANSCRIPT_MAX_RETRIES - 1) {
55
+ await new Promise((resolve) => setTimeout(resolve, TRANSCRIPT_RETRY_INTERVAL_MS))
56
+ }
57
+ }
58
+ return { text: NO_AUDIO_BE_MESSAGE }
59
+ }
60
+
61
+ public readonly loadTranscriptForAttempt = createEffect(async (attemptNumber: number) => {
62
+ const item = this.collection.get(attemptNumber)
63
+ if (!item?.audioFileId) return
64
+ if (item.transcript || item.transcriptLoading) return
65
+
66
+ this.collection.update(attemptNumber, { transcriptLoading: true })
67
+
68
+ try {
69
+ const { text, language } = await this.fetchTranscriptWithRetry(item.audioFileId)
70
+
71
+ this.collection.update(attemptNumber, {
72
+ transcript: text,
73
+ transcriptLanguage: language,
74
+ transcriptLoading: false,
75
+ transcriptError: undefined,
76
+ })
77
+ } catch (error) {
78
+ this.collection.update(attemptNumber, {
79
+ transcriptLoading: false,
80
+ transcriptError: error instanceof Error ? error.message : 'Failed to load transcript',
81
+ })
82
+ }
83
+ })
84
+
85
+ public getCachedTranscript(audioFileId: string): TranscriptResult | undefined {
86
+ return this.transcriptCache.get(audioFileId)
87
+ }
88
+ }
@@ -0,0 +1,75 @@
1
+ import { createEffect, createEvent, createStore, restore } from 'effector'
2
+ import { TranscriptionsCollection } from './TranscriptionsCollection'
3
+ import { VoicePlayerApi } from '../../types'
4
+ import { TranslationService } from '../../../translation/model/TranslationService'
5
+ import { GoogleTranslationProvider } from '../../../translation/providers/google/GoogleTranslationProvider'
6
+ import {
7
+ preservedWordsReplacers,
8
+ wordsToPreserve,
9
+ } from '../../../translation/preserve/wordsToPreserve'
10
+
11
+ type TranslateParams = {
12
+ attemptNumber: number
13
+ targetLanguage: string
14
+ }
15
+
16
+ type TranslateResult = {
17
+ attemptNumber: number
18
+ text: string
19
+ }
20
+
21
+ export class TranslationModel {
22
+ private readonly collection: TranscriptionsCollection
23
+ private readonly service: TranslationService
24
+
25
+ public readonly toggle = createEvent()
26
+ public readonly setActive = createEvent<boolean>()
27
+ public readonly clearCache = createEvent()
28
+ public readonly reset = createEvent()
29
+
30
+ public readonly $isActive = restore(this.setActive, false)
31
+ .on(this.toggle, (state) => !state)
32
+ .reset(this.reset)
33
+
34
+ public readonly translate = createEffect<TranslateParams, TranslateResult | null>(
35
+ async ({ attemptNumber, targetLanguage }) => {
36
+ const item = this.collection.get(attemptNumber)
37
+ if (!item?.transcript) return null
38
+
39
+ const text = await this.service.translate(item.transcript, {
40
+ targetLang: targetLanguage,
41
+ sourceLang: item.transcriptLanguage,
42
+ cacheKey: `voice::${attemptNumber}::${targetLanguage}`,
43
+ })
44
+
45
+ if (!text) return null
46
+
47
+ return { attemptNumber, text }
48
+ },
49
+ )
50
+
51
+ public readonly $translations = createStore<Record<number, string>>({})
52
+ .on(this.translate.doneData, (state, result) => {
53
+ if (!result) return state
54
+ return { ...state, [result.attemptNumber]: result.text }
55
+ })
56
+ .reset(this.reset, this.clearCache)
57
+
58
+ constructor(collection: TranscriptionsCollection, api: VoicePlayerApi) {
59
+ this.collection = collection
60
+ this.service = new TranslationService({
61
+ preserve: {
62
+ map: wordsToPreserve,
63
+ replacers: preservedWordsReplacers,
64
+ },
65
+ providers: [new GoogleTranslationProvider({ api: { translateRequest: api.translateText } })],
66
+ })
67
+
68
+ this.clearCache.watch(() => this.service.reset())
69
+ this.reset.watch(() => this.service.reset())
70
+ }
71
+
72
+ public getTranslation(attemptNumber: number): string | undefined {
73
+ return this.$translations.getState()[attemptNumber]
74
+ }
75
+ }
@@ -13,9 +13,9 @@ type DownloadResult = {
13
13
  }
14
14
 
15
15
  export class VoiceFileDownloaderModel {
16
- private api!: VoicePlayerApi
16
+ private readonly api: VoicePlayerApi
17
17
 
18
- public readonly setApi = (api: VoicePlayerApi) => {
18
+ constructor(api: VoicePlayerApi) {
19
19
  this.api = api
20
20
  }
21
21