@magmamath/students-features 0.2.22 → 0.3.0-rc.0

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 (127) hide show
  1. package/dist/commonjs/features/chatbot/components/Alternatives/Alternatives.js +8 -10
  2. package/dist/commonjs/features/chatbot/components/Alternatives/Alternatives.js.map +1 -1
  3. package/dist/commonjs/features/chatbot/components/ChatControls/ChatControls.js +6 -15
  4. package/dist/commonjs/features/chatbot/components/ChatControls/ChatControls.js.map +1 -1
  5. package/dist/commonjs/features/chatbot/components/ChatMessage/MessageLoader.js +3 -4
  6. package/dist/commonjs/features/chatbot/components/ChatMessage/MessageLoader.js.map +1 -1
  7. package/dist/commonjs/features/chatbot/components/ChatMessage/MessageTextToSpeech.js +3 -2
  8. package/dist/commonjs/features/chatbot/components/ChatMessage/MessageTextToSpeech.js.map +1 -1
  9. package/dist/commonjs/features/chatbot/components/ChatMessage/ReceivedMessage.js +4 -5
  10. package/dist/commonjs/features/chatbot/components/ChatMessage/ReceivedMessage.js.map +1 -1
  11. package/dist/commonjs/features/chatbot/components/ChatMessages/ChatbotMessages.js +14 -23
  12. package/dist/commonjs/features/chatbot/components/ChatMessages/ChatbotMessages.js.map +1 -1
  13. package/dist/commonjs/features/chatbot/components/Chatbot.js +6 -9
  14. package/dist/commonjs/features/chatbot/components/Chatbot.js.map +1 -1
  15. package/dist/commonjs/features/chatbot/index.js +2 -2
  16. package/dist/commonjs/features/chatbot/index.js.map +1 -1
  17. package/dist/commonjs/features/chatbot/model/{model.js → ChatBotModel.js} +83 -117
  18. package/dist/commonjs/features/chatbot/model/ChatBotModel.js.map +1 -0
  19. package/dist/commonjs/features/chatbot/model/t2s.js +7 -4
  20. package/dist/commonjs/features/chatbot/model/t2s.js.map +1 -1
  21. package/dist/commonjs/features/chatbot/model/translation.js +15 -30
  22. package/dist/commonjs/features/chatbot/model/translation.js.map +1 -1
  23. package/dist/commonjs/features/chatbot/types/model.types.js.map +1 -1
  24. package/dist/commonjs/features/feedback/model/model.js +1 -1
  25. package/dist/commonjs/features/feedback/model/model.js.map +1 -1
  26. package/dist/commonjs/lib/effector/createEntityFieldStore.js +8 -26
  27. package/dist/commonjs/lib/effector/createEntityFieldStore.js.map +1 -1
  28. package/dist/commonjs/shared/icons/ChatbotIcon.js +106 -613
  29. package/dist/commonjs/shared/icons/ChatbotIcon.js.map +1 -1
  30. package/dist/module/features/chatbot/components/Alternatives/Alternatives.js +6 -8
  31. package/dist/module/features/chatbot/components/Alternatives/Alternatives.js.map +1 -1
  32. package/dist/module/features/chatbot/components/ChatControls/ChatControls.js +6 -15
  33. package/dist/module/features/chatbot/components/ChatControls/ChatControls.js.map +1 -1
  34. package/dist/module/features/chatbot/components/ChatMessage/MessageLoader.js +3 -4
  35. package/dist/module/features/chatbot/components/ChatMessage/MessageLoader.js.map +1 -1
  36. package/dist/module/features/chatbot/components/ChatMessage/MessageTextToSpeech.js +3 -2
  37. package/dist/module/features/chatbot/components/ChatMessage/MessageTextToSpeech.js.map +1 -1
  38. package/dist/module/features/chatbot/components/ChatMessage/ReceivedMessage.js +4 -5
  39. package/dist/module/features/chatbot/components/ChatMessage/ReceivedMessage.js.map +1 -1
  40. package/dist/module/features/chatbot/components/ChatMessages/ChatbotMessages.js +14 -23
  41. package/dist/module/features/chatbot/components/ChatMessages/ChatbotMessages.js.map +1 -1
  42. package/dist/module/features/chatbot/components/Chatbot.js +6 -9
  43. package/dist/module/features/chatbot/components/Chatbot.js.map +1 -1
  44. package/dist/module/features/chatbot/index.js +1 -1
  45. package/dist/module/features/chatbot/index.js.map +1 -1
  46. package/dist/module/features/chatbot/model/ChatBotModel.js +208 -0
  47. package/dist/module/features/chatbot/model/ChatBotModel.js.map +1 -0
  48. package/dist/module/features/chatbot/model/t2s.js +8 -5
  49. package/dist/module/features/chatbot/model/t2s.js.map +1 -1
  50. package/dist/module/features/chatbot/model/translation.js +15 -30
  51. package/dist/module/features/chatbot/model/translation.js.map +1 -1
  52. package/dist/module/features/chatbot/types/model.types.js.map +1 -1
  53. package/dist/module/features/feedback/model/model.js +1 -1
  54. package/dist/module/features/feedback/model/model.js.map +1 -1
  55. package/dist/module/lib/effector/createEntityFieldStore.js +8 -26
  56. package/dist/module/lib/effector/createEntityFieldStore.js.map +1 -1
  57. package/dist/module/shared/icons/ChatbotIcon.js +107 -614
  58. package/dist/module/shared/icons/ChatbotIcon.js.map +1 -1
  59. package/dist/typescript/commonjs/features/chatbot/components/Alternatives/Alternatives.d.ts +1 -1
  60. package/dist/typescript/commonjs/features/chatbot/components/Alternatives/Alternatives.d.ts.map +1 -1
  61. package/dist/typescript/commonjs/features/chatbot/components/ChatControls/ChatControls.d.ts +2 -6
  62. package/dist/typescript/commonjs/features/chatbot/components/ChatControls/ChatControls.d.ts.map +1 -1
  63. package/dist/typescript/commonjs/features/chatbot/components/ChatMessages/ChatbotMessages.d.ts +1 -1
  64. package/dist/typescript/commonjs/features/chatbot/components/ChatMessages/ChatbotMessages.d.ts.map +1 -1
  65. package/dist/typescript/commonjs/features/chatbot/components/Chatbot.d.ts +2 -6
  66. package/dist/typescript/commonjs/features/chatbot/components/Chatbot.d.ts.map +1 -1
  67. package/dist/typescript/commonjs/features/chatbot/index.d.ts +1 -1
  68. package/dist/typescript/commonjs/features/chatbot/index.d.ts.map +1 -1
  69. package/dist/typescript/commonjs/features/chatbot/model/{model.d.ts → ChatBotModel.d.ts} +19 -35
  70. package/dist/typescript/commonjs/features/chatbot/model/ChatBotModel.d.ts.map +1 -0
  71. package/dist/typescript/commonjs/features/chatbot/model/t2s.d.ts +1 -1
  72. package/dist/typescript/commonjs/features/chatbot/model/t2s.d.ts.map +1 -1
  73. package/dist/typescript/commonjs/features/chatbot/model/translation.d.ts +1 -1
  74. package/dist/typescript/commonjs/features/chatbot/model/translation.d.ts.map +1 -1
  75. package/dist/typescript/commonjs/features/chatbot/types/model.types.d.ts +8 -20
  76. package/dist/typescript/commonjs/features/chatbot/types/model.types.d.ts.map +1 -1
  77. package/dist/typescript/commonjs/features/chatbot/types/translation.types.d.ts +1 -5
  78. package/dist/typescript/commonjs/features/chatbot/types/translation.types.d.ts.map +1 -1
  79. package/dist/typescript/commonjs/lib/effector/createEntityFieldStore.d.ts +4 -9
  80. package/dist/typescript/commonjs/lib/effector/createEntityFieldStore.d.ts.map +1 -1
  81. package/dist/typescript/commonjs/shared/icons/ChatbotIcon.d.ts.map +1 -1
  82. package/dist/typescript/module/features/chatbot/components/Alternatives/Alternatives.d.ts +1 -1
  83. package/dist/typescript/module/features/chatbot/components/Alternatives/Alternatives.d.ts.map +1 -1
  84. package/dist/typescript/module/features/chatbot/components/ChatControls/ChatControls.d.ts +2 -6
  85. package/dist/typescript/module/features/chatbot/components/ChatControls/ChatControls.d.ts.map +1 -1
  86. package/dist/typescript/module/features/chatbot/components/ChatMessages/ChatbotMessages.d.ts +1 -1
  87. package/dist/typescript/module/features/chatbot/components/ChatMessages/ChatbotMessages.d.ts.map +1 -1
  88. package/dist/typescript/module/features/chatbot/components/Chatbot.d.ts +2 -6
  89. package/dist/typescript/module/features/chatbot/components/Chatbot.d.ts.map +1 -1
  90. package/dist/typescript/module/features/chatbot/index.d.ts +1 -1
  91. package/dist/typescript/module/features/chatbot/index.d.ts.map +1 -1
  92. package/dist/typescript/module/features/chatbot/model/{model.d.ts → ChatBotModel.d.ts} +19 -35
  93. package/dist/typescript/module/features/chatbot/model/ChatBotModel.d.ts.map +1 -0
  94. package/dist/typescript/module/features/chatbot/model/t2s.d.ts +1 -1
  95. package/dist/typescript/module/features/chatbot/model/t2s.d.ts.map +1 -1
  96. package/dist/typescript/module/features/chatbot/model/translation.d.ts +1 -1
  97. package/dist/typescript/module/features/chatbot/model/translation.d.ts.map +1 -1
  98. package/dist/typescript/module/features/chatbot/types/model.types.d.ts +8 -20
  99. package/dist/typescript/module/features/chatbot/types/model.types.d.ts.map +1 -1
  100. package/dist/typescript/module/features/chatbot/types/translation.types.d.ts +1 -5
  101. package/dist/typescript/module/features/chatbot/types/translation.types.d.ts.map +1 -1
  102. package/dist/typescript/module/lib/effector/createEntityFieldStore.d.ts +4 -9
  103. package/dist/typescript/module/lib/effector/createEntityFieldStore.d.ts.map +1 -1
  104. package/dist/typescript/module/shared/icons/ChatbotIcon.d.ts.map +1 -1
  105. package/package.json +1 -1
  106. package/src/features/chatbot/components/Alternatives/Alternatives.tsx +7 -10
  107. package/src/features/chatbot/components/ChatControls/ChatControls.tsx +5 -10
  108. package/src/features/chatbot/components/ChatMessage/MessageLoader.tsx +2 -3
  109. package/src/features/chatbot/components/ChatMessage/MessageTextToSpeech.tsx +2 -1
  110. package/src/features/chatbot/components/ChatMessage/ReceivedMessage.tsx +3 -4
  111. package/src/features/chatbot/components/ChatMessages/ChatbotMessages.tsx +15 -24
  112. package/src/features/chatbot/components/Chatbot.tsx +6 -10
  113. package/src/features/chatbot/index.ts +1 -1
  114. package/src/features/chatbot/model/ChatBotModel.ts +222 -0
  115. package/src/features/chatbot/model/t2s.ts +8 -5
  116. package/src/features/chatbot/model/translation.ts +181 -201
  117. package/src/features/chatbot/types/model.types.ts +8 -14
  118. package/src/features/chatbot/types/translation.types.ts +10 -12
  119. package/src/features/feedback/model/model.ts +1 -1
  120. package/src/lib/effector/createEntityFieldStore.ts +9 -35
  121. package/src/shared/icons/ChatbotIcon.tsx +85 -500
  122. package/dist/commonjs/features/chatbot/model/model.js.map +0 -1
  123. package/dist/module/features/chatbot/model/model.js +0 -242
  124. package/dist/module/features/chatbot/model/model.js.map +0 -1
  125. package/dist/typescript/commonjs/features/chatbot/model/model.d.ts.map +0 -1
  126. package/dist/typescript/module/features/chatbot/model/model.d.ts.map +0 -1
  127. package/src/features/chatbot/model/model.ts +0 -268
@@ -0,0 +1,222 @@
1
+ import {
2
+ attach,
3
+ createEvent,
4
+ restore,
5
+ sample,
6
+ type Store,
7
+ type Effect,
8
+ combine,
9
+ createStore,
10
+ } from 'effector'
11
+ import { createControllerEffect, createEntityFieldStore } from '../../../lib/effector'
12
+
13
+ import {
14
+ ChatbotModelProps,
15
+ ConversationMessage,
16
+ SendHelpParams,
17
+ SendHelpProps,
18
+ SetHintMessageProps,
19
+ } from '../types/model.types'
20
+ import { GetAlternativesResponse, PostMessageResponse } from '../types/api.types'
21
+
22
+ import { generateRandomId } from '../../../lib/helpers/helpers'
23
+ import { ChatbotAPI } from './api'
24
+ import { ChatbotTranslation } from './translation'
25
+ import { ChatTextToSpeechModel } from './t2s'
26
+
27
+ export class ChatbotModel {
28
+ readonly api: ChatbotAPI
29
+ readonly translation: ChatbotTranslation
30
+ readonly t2s: ChatTextToSpeechModel
31
+
32
+ readonly credits: ChatbotModelProps['credits']
33
+ public readonly $currentKey = createStore('')
34
+
35
+ readonly resetAlternatives = createEvent()
36
+ readonly setAlternatives = createEvent<GetAlternativesResponse | null>()
37
+ readonly $alternatives: Store<GetAlternativesResponse | null>
38
+
39
+ readonly setIsOpen = createEvent<boolean>()
40
+ readonly toggleOpen = createEvent()
41
+ readonly $isOpen = restore(this.setIsOpen, false).on(this.toggleOpen, isOpen => !isOpen)
42
+
43
+ // separate messages and alternatives class?
44
+ public readonly removeLastMessage = createEvent()
45
+ private readonly setLastMessageKey = createEvent<string>()
46
+ private readonly $lastMessageKey = restore(this.setLastMessageKey, null)
47
+
48
+ readonly setScrollActive = createEvent<boolean>()
49
+ readonly $isScrollActive = restore(this.setScrollActive, false)
50
+
51
+ readonly sendHelpRequestFx: Effect<SendHelpParams, PostMessageResponse | ConversationMessage>
52
+
53
+ readonly defaultMessages: ChatbotModelProps['messages']
54
+
55
+ constructor({ credits, api, messages, $key }: ChatbotModelProps) {
56
+ this.api = new ChatbotAPI(api)
57
+
58
+ this.defaultMessages = messages
59
+ this.credits = credits
60
+ if ($key) {
61
+ sample({
62
+ source: $key,
63
+ target: this.$currentKey,
64
+ })
65
+ } else if (credits) {
66
+ sample({
67
+ source: combine(
68
+ credits,
69
+ ({ $assignmentId, $exerciseId }) => `${$assignmentId}-${$exerciseId}`,
70
+ ),
71
+ target: this.$currentKey,
72
+ })
73
+ }
74
+
75
+ this.$alternatives = restore(this.api.getAlternativesFx.doneData, null)
76
+ .on(this.setAlternatives, (_, payload) => payload)
77
+ .reset(this.resetAlternatives)
78
+
79
+ // TODO: do not pass this, just what you need
80
+ this.translation = new ChatbotTranslation(this)
81
+ this.t2s = new ChatTextToSpeechModel({ $isOpen: this.$isOpen, api: this.api })
82
+
83
+ this.sendHelpRequestFx = this.createSendHelpRequestFx()
84
+ this.initChatReplyWatcher()
85
+ }
86
+
87
+ readonly messages = createEntityFieldStore<ConversationMessage[]>()
88
+ readonly threadIds = createEntityFieldStore<string>()
89
+ readonly availableChats = createEntityFieldStore<boolean>()
90
+
91
+ private createSendHelpRequestFx() {
92
+ return attach({
93
+ source: [this.$currentKey, this.messages.$state, this.translation.$isTranslated],
94
+ mapParams: (params: SendHelpParams, [key, messages, isTranslated]) => ({
95
+ ...params,
96
+ key,
97
+ messages,
98
+ isTranslated,
99
+ }),
100
+ effect: createControllerEffect(
101
+ async ({
102
+ payload,
103
+ message = '',
104
+ translationTargetLanguage,
105
+ translatedMessage,
106
+ key,
107
+ messages,
108
+ isTranslated,
109
+ }: SendHelpProps) => {
110
+ this.setLastMessageKey(key)
111
+
112
+ const currentMessages = messages[key] || []
113
+
114
+ this.messages.setField({
115
+ key,
116
+ data: [
117
+ ...currentMessages,
118
+ {
119
+ id: generateRandomId(),
120
+ message: message ?? '',
121
+ translatedMessage,
122
+ isOwnMessage: true,
123
+ threadId: payload.threadId ?? '',
124
+ options: [],
125
+ },
126
+ ],
127
+ })
128
+
129
+ const response = await this.api.postMessageFx(payload)
130
+ if (isTranslated) {
131
+ return await this.translation.translateMessageFx({
132
+ targetLanguage: translationTargetLanguage as string,
133
+ message: {
134
+ ...response,
135
+ id: generateRandomId(),
136
+ isOwnMessage: false,
137
+ },
138
+ })
139
+ }
140
+
141
+ return response
142
+ },
143
+ ),
144
+ })
145
+ }
146
+
147
+ private initChatReplyWatcher() {
148
+ sample({
149
+ clock: this.sendHelpRequestFx.doneData,
150
+ source: [this.$currentKey, this.messages.$state] as const,
151
+ fn: ([key, messages], response) => ({ key, messages: messages[key] || [], response }),
152
+ }).watch(({ key, messages, response }) => {
153
+ this.messages.setField({
154
+ key,
155
+ data: [
156
+ ...messages,
157
+ {
158
+ id: generateRandomId(),
159
+ message: response.message,
160
+ isOwnMessage: false,
161
+ threadId: response.threadId,
162
+ options: response.options,
163
+ translatedMessage:
164
+ 'translatedMessage' in response ? response.translatedMessage : undefined,
165
+ },
166
+ ],
167
+ })
168
+
169
+ this.threadIds.setField({ key, data: response.threadId })
170
+ })
171
+
172
+ sample({
173
+ clock: this.sendHelpRequestFx.fail,
174
+ source: [this.$currentKey, this.messages.$state] as const,
175
+ fn: ([key, messages]) => ({ key, data: [...messages[key], this.defaultMessages.error] }),
176
+ filter: (_, { error }) => error.message !== 'Request aborted manually',
177
+ target: this.messages.setField,
178
+ })
179
+
180
+ sample({
181
+ clock: this.removeLastMessage,
182
+ source: [this.$lastMessageKey, this.messages.$state] as const,
183
+ fn: ([lastMessageKey, messages]) => {
184
+ const mockData = { key: '', data: [] }
185
+ if (!lastMessageKey) return mockData
186
+ const updatedMessages = messages[lastMessageKey]
187
+ if (!updatedMessages?.length) return mockData
188
+ updatedMessages.pop()
189
+ return { key: lastMessageKey, data: updatedMessages }
190
+ },
191
+ filter: ([lastMessageKey]) => !!lastMessageKey,
192
+ target: this.messages.setField,
193
+ })
194
+ }
195
+
196
+ public startConversation() {
197
+ const key = this.$currentKey.getState()
198
+ const messages = this.messages.$state.getState()[key] ?? []
199
+
200
+ if (messages.length) return
201
+
202
+ this.messages.setField({ key, data: [this.defaultMessages.start] })
203
+ this.availableChats.setField({ key, data: true })
204
+ }
205
+
206
+ public setHintMessage({ entityId, fieldId, ...message }: SetHintMessageProps) {
207
+ const options = this.$alternatives.getState()?.options.hints ?? message.options
208
+ const messagesList = this.messages.$state.getState()
209
+ const key = `${entityId}-${fieldId}`
210
+ const messages = messagesList[key]
211
+ if (messages?.length > 1) return
212
+ this.messages.setField({ key, data: [{ ...message, options }] })
213
+ }
214
+
215
+ public bindKey($store: Store<string>) {
216
+ sample({
217
+ source: $store,
218
+ target: this.$currentKey,
219
+ })
220
+ return this
221
+ }
222
+ }
@@ -1,7 +1,8 @@
1
- import { restore, createEvent, Store } from 'effector'
1
+ import { restore, createEvent, Store, sample } from 'effector'
2
2
  import { AUDIO_CONFIG, DEFAULT_VOICE } from '../constants'
3
3
  import { AudioProvider, AudioStatus, PlayTextToSpeechProps } from '../types/t2s.types'
4
4
  import { ChatbotAPI } from './api'
5
+ import { createEffect } from 'effector/effector.umd'
5
6
 
6
7
  type ChatTextToSpeechModelProps = {
7
8
  $isOpen: Store<boolean>
@@ -21,8 +22,10 @@ export class ChatTextToSpeechModel {
21
22
  constructor({ $isOpen, api }: ChatTextToSpeechModelProps) {
22
23
  this.api = api
23
24
 
24
- $isOpen.updates.watch(isOpen => {
25
- if (!isOpen) this.reset()
25
+ sample({
26
+ source: $isOpen.updates,
27
+ filter: isOpen => !isOpen,
28
+ target: this.reset,
26
29
  })
27
30
  }
28
31
 
@@ -97,9 +100,9 @@ export class ChatTextToSpeechModel {
97
100
  this.audioProvider.stop()
98
101
  }
99
102
 
100
- public reset() {
103
+ public readonly reset = createEffect(() => {
101
104
  this.stop()
102
105
  this.setStatus(AudioStatus.STOPPED)
103
106
  this.setCurrentPlayingId(null)
104
- }
107
+ })
105
108
  }
@@ -1,231 +1,211 @@
1
1
  import { createEvent, restore, attach, createEffect, type Effect, sample } from 'effector'
2
- import { ChatbotModel } from './model'
2
+ import { ChatbotModel } from './ChatBotModel'
3
3
 
4
4
  import {
5
- TranslateAlternativesFxProps,
6
- TranslateBatchFxProps,
7
- TranslateMessageFxProps,
5
+ TranslateAlternativesFxProps,
6
+ TranslateBatchFxProps,
7
+ TranslateMessageFxProps,
8
8
  } from '../types/translation.types'
9
9
  import { ConversationMessage } from '../types/model.types'
10
10
  import { GetAlternativesResponse } from '../types/api.types'
11
11
  import { decodeHTMLEntities } from '../helpers'
12
12
 
13
13
  export class ChatbotTranslation {
14
- private readonly model: ChatbotModel
14
+ private readonly model: ChatbotModel
15
15
 
16
- readonly setIsTranslated = createEvent<boolean>()
17
- readonly $isTranslated = restore(this.setIsTranslated, false)
16
+ readonly setIsTranslated = createEvent<boolean>()
17
+ readonly $isTranslated = restore(this.setIsTranslated, false)
18
18
 
19
- readonly translateMessageFx: Effect<TranslateMessageFxProps, ConversationMessage>
20
- readonly translateBatchFx: Effect<TranslateBatchFxProps['params'], ConversationMessage[]>
21
- readonly translateAlternativesFx: Effect<
22
- TranslateAlternativesFxProps['params'],
23
- GetAlternativesResponse | null
24
- >
19
+ readonly translateMessageFx: Effect<TranslateMessageFxProps, ConversationMessage>
20
+ readonly translateBatchFx: Effect<TranslateBatchFxProps['params'], ConversationMessage[]>
21
+ readonly translateAlternativesFx: Effect<
22
+ TranslateAlternativesFxProps['params'],
23
+ GetAlternativesResponse | null
24
+ >
25
25
 
26
- constructor(chatbotModel: ChatbotModel) {
27
- this.model = chatbotModel
26
+ constructor(chatbotModel: ChatbotModel) {
27
+ this.model = chatbotModel
28
28
 
29
- this.translateAlternativesFx = this.createTranslateAlternativesEffect()
30
- this.translateMessageFx = this.createTranslateMessageEffect()
31
- this.translateBatchFx = this.createTranslateBatchEffect()
29
+ this.translateAlternativesFx = this.createTranslateAlternativesEffect()
30
+ this.translateMessageFx = this.createTranslateMessageEffect()
31
+ this.translateBatchFx = this.createTranslateBatchEffect()
32
32
 
33
- this.initTranslationSamples()
34
- }
35
-
36
- private shouldSkipTranslation(messages: ConversationMessage[]) {
37
- if (this.$isTranslated.getState()) {
38
- this.setIsTranslated(false)
39
- return true
40
- }
41
-
42
- const allTranslated = messages.every(message => message.translatedMessage)
43
- if (allTranslated) {
44
- this.setIsTranslated(true)
45
- return true
46
- }
33
+ this.initTranslationSamples()
34
+ }
47
35
 
48
- return false
36
+ private shouldSkipTranslation(messages: ConversationMessage[]) {
37
+ if (this.$isTranslated.getState()) {
38
+ this.setIsTranslated(false)
39
+ return true
49
40
  }
50
41
 
51
- private initTranslationSamples() {
52
- sample({
53
- clock: this.translateBatchFx.doneData,
54
- source: [this.model.credits.$assignmentId, this.model.credits.$exerciseId] as const,
55
- fn: ([assignmentId, exerciseId], translatedMessages) => ({
56
- assignmentId,
57
- exerciseId,
58
- translatedMessages,
59
- }),
60
- }).watch(({ assignmentId, exerciseId, translatedMessages }) => {
61
- this.model.messages.setField({
62
- entityId: assignmentId,
63
- fieldId: exerciseId,
64
- messages: translatedMessages,
65
- })
66
- })
42
+ const allTranslated = messages.every(message => message.translatedMessage)
43
+ if (allTranslated) {
44
+ this.setIsTranslated(true)
45
+ return true
67
46
  }
68
47
 
69
- private createTranslateAlternativesEffect() {
70
- return attach({
71
- source: [this.model.$alternatives],
72
- mapParams: (params: TranslateAlternativesFxProps['params'], [alternatives]) => ({
73
- params,
74
- alternatives,
75
- }),
76
- effect: createEffect(async ({ alternatives, params }: TranslateAlternativesFxProps) => {
77
- const hintAlternatives = alternatives?.options.hints ?? []
78
- const defaultAlternatives = alternatives?.options.default ?? []
79
-
80
- const translatedAlternatives = { ...alternatives }
81
-
82
- if (hintAlternatives.length) {
83
- const hintText = hintAlternatives.map(({ text }) => text)
84
- const response = await this.model.api.translateTextFx({
85
- text: hintText,
86
- targetLanguage: params.targetLanguage,
87
- })
88
-
89
- if (translatedAlternatives.options) {
90
- translatedAlternatives.options.hints = hintAlternatives.map(
91
- (alternative, index) => ({
92
- ...alternative,
93
- translatedMessage: decodeHTMLEntities(
94
- response.data.translations[index].translatedText,
95
- ),
96
- }),
97
- )
98
- }
99
- }
100
-
101
- if (defaultAlternatives.length) {
102
- const defaultText = defaultAlternatives.map(({ text }) => text)
103
- const response = await this.model.api.translateTextFx({
104
- text: defaultText,
105
- targetLanguage: params.targetLanguage,
106
- })
107
-
108
- if (translatedAlternatives.options) {
109
- translatedAlternatives.options.default = defaultAlternatives.map(
110
- (alternative, index) => ({
111
- ...alternative,
112
- translatedText: decodeHTMLEntities(
113
- response.data.translations[index].translatedText,
114
- ),
115
- }),
116
- )
117
- }
118
- }
119
-
120
- return translatedAlternatives as GetAlternativesResponse | null
121
- }),
122
- })
123
- }
124
-
125
- private createTranslateMessageEffect() {
126
- return createEffect(async ({ message, targetLanguage }: TranslateMessageFxProps) => {
127
- const optionsText = message.options.map(({ text }) => text)
128
- const response = await this.model.api.translateTextFx({
129
- text: [message.message, ...optionsText],
130
- targetLanguage,
131
- })
132
-
133
- const translatedOptions = response.data.translations.slice(1)
134
- const optionsCopy = [...message.options]
135
- const newOptions = optionsCopy.map((option, index) => ({
136
- ...option,
137
- translatedText: decodeHTMLEntities(translatedOptions[index].translatedText),
48
+ return false
49
+ }
50
+
51
+ private initTranslationSamples() {
52
+ sample({
53
+ clock: this.translateBatchFx.doneData,
54
+ source: [this.model.$currentKey] as const,
55
+ fn: ([key], translatedMessages) => ({ key, data: translatedMessages }),
56
+ target: this.model.messages.setField,
57
+ })
58
+ }
59
+
60
+ private createTranslateAlternativesEffect() {
61
+ return attach({
62
+ source: [this.model.$alternatives],
63
+ mapParams: (params: TranslateAlternativesFxProps['params'], [alternatives]) => ({
64
+ params,
65
+ alternatives,
66
+ }),
67
+ effect: createEffect(async ({ alternatives, params }: TranslateAlternativesFxProps) => {
68
+ const hintAlternatives = alternatives?.options.hints ?? []
69
+ const defaultAlternatives = alternatives?.options.default ?? []
70
+
71
+ const translatedAlternatives = { ...alternatives }
72
+
73
+ if (hintAlternatives.length) {
74
+ const hintText = hintAlternatives.map(({ text }) => text)
75
+ const response = await this.model.api.translateTextFx({
76
+ text: hintText,
77
+ targetLanguage: params.targetLanguage,
78
+ })
79
+
80
+ if (translatedAlternatives.options) {
81
+ translatedAlternatives.options.hints = hintAlternatives.map((alternative, index) => ({
82
+ ...alternative,
83
+ translatedMessage: decodeHTMLEntities(
84
+ response.data.translations[index].translatedText,
85
+ ),
138
86
  }))
87
+ }
88
+ }
139
89
 
140
- return {
141
- ...message,
142
- translatedMessage: decodeHTMLEntities(response.data.translations[0].translatedText),
143
- options: newOptions,
144
- } as ConversationMessage
145
- })
146
- }
147
-
148
- private createTranslateBatchEffect() {
149
- return attach({
150
- source: [this.model.credits.$assignmentId, this.model.credits.$exerciseId, this.model.messages.$state],
151
- mapParams: (
152
- params: TranslateBatchFxProps['params'],
153
- [assignmentId, exerciseId, messages],
154
- ) => ({
155
- params,
156
- assignmentId,
157
- exerciseId,
158
- messages,
159
- }),
160
- effect: createEffect(
161
- async ({ messages, assignmentId, exerciseId, params }: TranslateBatchFxProps) => {
162
- const currentMessages = messages[assignmentId]?.[exerciseId]?.messages ?? []
163
-
164
- if (this.shouldSkipTranslation(currentMessages)) {
165
- return currentMessages
166
- }
167
-
168
- const messagesText = currentMessages.map(({ message }) => message)
169
- const response = await this.model.api.translateTextFx({
170
- text: messagesText,
171
- targetLanguage: params.targetLanguage,
172
- })
173
-
174
- const lastMessage = currentMessages.at(-1)
175
- const lastMessageTranslatedOptions = lastMessage
176
- ? await this.translateMessageOptions(lastMessage, params.targetLanguage)
177
- : undefined
178
-
179
- const errorMessageTranslationResponse = await this.model.api.translateTextFx({
180
- text: [this.model.defaultMessages.error.message],
181
- targetLanguage: params.targetLanguage,
182
- })
183
-
184
- this.model.defaultMessages.error.translatedMessage = decodeHTMLEntities(
185
- errorMessageTranslationResponse.data.translations[0].translatedText,
186
- )
187
-
188
- // TODO: move this AFTER state update in sample and remove initTranslationSamples it is not needed
189
- // TODO: maybe just one request to translate everything
190
- this.setIsTranslated(true)
191
-
192
- const translatedMessages = response.data.translations.map(
193
- ({ translatedText }) => translatedText,
194
- )
195
-
196
- return currentMessages.map((message, index) => {
197
- const isLastMessage = index === currentMessages.length - 1
198
- return {
199
- ...message,
200
- options: isLastMessage ? lastMessageTranslatedOptions : message.options,
201
- translatedMessage: decodeHTMLEntities(translatedMessages[index]),
202
- }
203
- }) as ConversationMessage[]
204
- },
205
- ),
206
- })
207
- }
90
+ if (defaultAlternatives.length) {
91
+ const defaultText = defaultAlternatives.map(({ text }) => text)
92
+ const response = await this.model.api.translateTextFx({
93
+ text: defaultText,
94
+ targetLanguage: params.targetLanguage,
95
+ })
96
+
97
+ if (translatedAlternatives.options) {
98
+ translatedAlternatives.options.default = defaultAlternatives.map(
99
+ (alternative, index) => ({
100
+ ...alternative,
101
+ translatedText: decodeHTMLEntities(
102
+ response.data.translations[index].translatedText,
103
+ ),
104
+ }),
105
+ )
106
+ }
107
+ }
208
108
 
209
- private async translateMessageOptions(message: ConversationMessage, targetLanguage: string) {
210
- if (!message?.options?.length) {
211
- const translatedAlternatives = await this.translateAlternativesFx({ targetLanguage })
212
- this.model.setAlternatives(translatedAlternatives)
213
- return
109
+ return translatedAlternatives as GetAlternativesResponse | null
110
+ }),
111
+ })
112
+ }
113
+
114
+ private createTranslateMessageEffect() {
115
+ return createEffect(async ({ message, targetLanguage }: TranslateMessageFxProps) => {
116
+ const optionsText = message.options.map(({ text }) => text)
117
+ const response = await this.model.api.translateTextFx({
118
+ text: [message.message, ...optionsText],
119
+ targetLanguage,
120
+ })
121
+
122
+ const translatedOptions = response.data.translations.slice(1)
123
+ const optionsCopy = [...message.options]
124
+ const newOptions = optionsCopy.map((option, index) => ({
125
+ ...option,
126
+ translatedText: decodeHTMLEntities(translatedOptions[index].translatedText),
127
+ }))
128
+
129
+ return {
130
+ ...message,
131
+ translatedMessage: decodeHTMLEntities(response.data.translations[0].translatedText),
132
+ options: newOptions,
133
+ } as ConversationMessage
134
+ })
135
+ }
136
+
137
+ private createTranslateBatchEffect() {
138
+ return attach({
139
+ source: [this.model.$currentKey, this.model.messages.$state],
140
+ mapParams: (params: TranslateBatchFxProps['params'], [key, messages]) => ({
141
+ params,
142
+ messages: messages[key] ?? [],
143
+ }),
144
+ effect: createEffect(async ({ messages, params }: TranslateBatchFxProps) => {
145
+ if (this.shouldSkipTranslation(messages)) {
146
+ return messages
214
147
  }
215
148
 
216
- const optionsText = message.options.map(({ text }) => text)
149
+ const messagesText = messages.map(({ message }) => message)
217
150
  const response = await this.model.api.translateTextFx({
218
- text: optionsText,
219
- targetLanguage,
151
+ text: messagesText,
152
+ targetLanguage: params.targetLanguage,
220
153
  })
221
154
 
222
- return message.options.map((option, index) => ({
223
- ...option,
224
- translatedText: decodeHTMLEntities(response.data.translations[index].translatedText),
225
- }))
226
- }
155
+ const lastMessage = messages.at(-1)
156
+ const lastMessageTranslatedOptions = lastMessage
157
+ ? await this.translateMessageOptions(lastMessage, params.targetLanguage)
158
+ : undefined
227
159
 
228
- public reset() {
229
- this.setIsTranslated(false)
160
+ const errorMessageTranslationResponse = await this.model.api.translateTextFx({
161
+ text: [this.model.defaultMessages.error.message],
162
+ targetLanguage: params.targetLanguage,
163
+ })
164
+
165
+ this.model.defaultMessages.error.translatedMessage = decodeHTMLEntities(
166
+ errorMessageTranslationResponse.data.translations[0].translatedText,
167
+ )
168
+
169
+ // TODO: move this AFTER state update in sample and remove initTranslationSamples it is not needed
170
+ // TODO: maybe just one request to translate everything
171
+ this.setIsTranslated(true)
172
+
173
+ const translatedMessages = response.data.translations.map(
174
+ ({ translatedText }) => translatedText,
175
+ )
176
+
177
+ return messages.map((message, index) => {
178
+ const isLastMessage = index === messages.length - 1
179
+ return {
180
+ ...message,
181
+ options: isLastMessage ? lastMessageTranslatedOptions : message.options,
182
+ translatedMessage: decodeHTMLEntities(translatedMessages[index]),
183
+ }
184
+ }) as ConversationMessage[]
185
+ }),
186
+ })
187
+ }
188
+
189
+ private async translateMessageOptions(message: ConversationMessage, targetLanguage: string) {
190
+ if (!message?.options?.length) {
191
+ const translatedAlternatives = await this.translateAlternativesFx({ targetLanguage })
192
+ this.model.setAlternatives(translatedAlternatives)
193
+ return
230
194
  }
195
+
196
+ const optionsText = message.options.map(({ text }) => text)
197
+ const response = await this.model.api.translateTextFx({
198
+ text: optionsText,
199
+ targetLanguage,
200
+ })
201
+
202
+ return message.options.map((option, index) => ({
203
+ ...option,
204
+ translatedText: decodeHTMLEntities(response.data.translations[index].translatedText),
205
+ }))
206
+ }
207
+
208
+ public reset() {
209
+ this.setIsTranslated(false)
210
+ }
231
211
  }