@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.
- package/dist/commonjs/features/chatbot/components/Alternatives/Alternatives.js +8 -10
- package/dist/commonjs/features/chatbot/components/Alternatives/Alternatives.js.map +1 -1
- package/dist/commonjs/features/chatbot/components/ChatControls/ChatControls.js +6 -15
- package/dist/commonjs/features/chatbot/components/ChatControls/ChatControls.js.map +1 -1
- package/dist/commonjs/features/chatbot/components/ChatMessage/MessageLoader.js +3 -4
- package/dist/commonjs/features/chatbot/components/ChatMessage/MessageLoader.js.map +1 -1
- package/dist/commonjs/features/chatbot/components/ChatMessage/MessageTextToSpeech.js +3 -2
- package/dist/commonjs/features/chatbot/components/ChatMessage/MessageTextToSpeech.js.map +1 -1
- package/dist/commonjs/features/chatbot/components/ChatMessage/ReceivedMessage.js +4 -5
- package/dist/commonjs/features/chatbot/components/ChatMessage/ReceivedMessage.js.map +1 -1
- package/dist/commonjs/features/chatbot/components/ChatMessages/ChatbotMessages.js +14 -23
- package/dist/commonjs/features/chatbot/components/ChatMessages/ChatbotMessages.js.map +1 -1
- package/dist/commonjs/features/chatbot/components/Chatbot.js +6 -9
- package/dist/commonjs/features/chatbot/components/Chatbot.js.map +1 -1
- package/dist/commonjs/features/chatbot/index.js +2 -2
- package/dist/commonjs/features/chatbot/index.js.map +1 -1
- package/dist/commonjs/features/chatbot/model/{model.js → ChatBotModel.js} +83 -117
- package/dist/commonjs/features/chatbot/model/ChatBotModel.js.map +1 -0
- package/dist/commonjs/features/chatbot/model/t2s.js +7 -4
- package/dist/commonjs/features/chatbot/model/t2s.js.map +1 -1
- package/dist/commonjs/features/chatbot/model/translation.js +15 -30
- package/dist/commonjs/features/chatbot/model/translation.js.map +1 -1
- package/dist/commonjs/features/chatbot/types/model.types.js.map +1 -1
- package/dist/commonjs/features/feedback/model/model.js +1 -1
- package/dist/commonjs/features/feedback/model/model.js.map +1 -1
- package/dist/commonjs/lib/effector/createEntityFieldStore.js +8 -26
- package/dist/commonjs/lib/effector/createEntityFieldStore.js.map +1 -1
- package/dist/commonjs/shared/icons/ChatbotIcon.js +106 -613
- package/dist/commonjs/shared/icons/ChatbotIcon.js.map +1 -1
- package/dist/module/features/chatbot/components/Alternatives/Alternatives.js +6 -8
- package/dist/module/features/chatbot/components/Alternatives/Alternatives.js.map +1 -1
- package/dist/module/features/chatbot/components/ChatControls/ChatControls.js +6 -15
- package/dist/module/features/chatbot/components/ChatControls/ChatControls.js.map +1 -1
- package/dist/module/features/chatbot/components/ChatMessage/MessageLoader.js +3 -4
- package/dist/module/features/chatbot/components/ChatMessage/MessageLoader.js.map +1 -1
- package/dist/module/features/chatbot/components/ChatMessage/MessageTextToSpeech.js +3 -2
- package/dist/module/features/chatbot/components/ChatMessage/MessageTextToSpeech.js.map +1 -1
- package/dist/module/features/chatbot/components/ChatMessage/ReceivedMessage.js +4 -5
- package/dist/module/features/chatbot/components/ChatMessage/ReceivedMessage.js.map +1 -1
- package/dist/module/features/chatbot/components/ChatMessages/ChatbotMessages.js +14 -23
- package/dist/module/features/chatbot/components/ChatMessages/ChatbotMessages.js.map +1 -1
- package/dist/module/features/chatbot/components/Chatbot.js +6 -9
- package/dist/module/features/chatbot/components/Chatbot.js.map +1 -1
- package/dist/module/features/chatbot/index.js +1 -1
- package/dist/module/features/chatbot/index.js.map +1 -1
- package/dist/module/features/chatbot/model/ChatBotModel.js +208 -0
- package/dist/module/features/chatbot/model/ChatBotModel.js.map +1 -0
- package/dist/module/features/chatbot/model/t2s.js +8 -5
- package/dist/module/features/chatbot/model/t2s.js.map +1 -1
- package/dist/module/features/chatbot/model/translation.js +15 -30
- package/dist/module/features/chatbot/model/translation.js.map +1 -1
- package/dist/module/features/chatbot/types/model.types.js.map +1 -1
- package/dist/module/features/feedback/model/model.js +1 -1
- package/dist/module/features/feedback/model/model.js.map +1 -1
- package/dist/module/lib/effector/createEntityFieldStore.js +8 -26
- package/dist/module/lib/effector/createEntityFieldStore.js.map +1 -1
- package/dist/module/shared/icons/ChatbotIcon.js +107 -614
- package/dist/module/shared/icons/ChatbotIcon.js.map +1 -1
- package/dist/typescript/commonjs/features/chatbot/components/Alternatives/Alternatives.d.ts +1 -1
- package/dist/typescript/commonjs/features/chatbot/components/Alternatives/Alternatives.d.ts.map +1 -1
- package/dist/typescript/commonjs/features/chatbot/components/ChatControls/ChatControls.d.ts +2 -6
- package/dist/typescript/commonjs/features/chatbot/components/ChatControls/ChatControls.d.ts.map +1 -1
- package/dist/typescript/commonjs/features/chatbot/components/ChatMessages/ChatbotMessages.d.ts +1 -1
- package/dist/typescript/commonjs/features/chatbot/components/ChatMessages/ChatbotMessages.d.ts.map +1 -1
- package/dist/typescript/commonjs/features/chatbot/components/Chatbot.d.ts +2 -6
- package/dist/typescript/commonjs/features/chatbot/components/Chatbot.d.ts.map +1 -1
- package/dist/typescript/commonjs/features/chatbot/index.d.ts +1 -1
- package/dist/typescript/commonjs/features/chatbot/index.d.ts.map +1 -1
- package/dist/typescript/commonjs/features/chatbot/model/{model.d.ts → ChatBotModel.d.ts} +19 -35
- package/dist/typescript/commonjs/features/chatbot/model/ChatBotModel.d.ts.map +1 -0
- package/dist/typescript/commonjs/features/chatbot/model/t2s.d.ts +1 -1
- package/dist/typescript/commonjs/features/chatbot/model/t2s.d.ts.map +1 -1
- package/dist/typescript/commonjs/features/chatbot/model/translation.d.ts +1 -1
- package/dist/typescript/commonjs/features/chatbot/model/translation.d.ts.map +1 -1
- package/dist/typescript/commonjs/features/chatbot/types/model.types.d.ts +8 -20
- package/dist/typescript/commonjs/features/chatbot/types/model.types.d.ts.map +1 -1
- package/dist/typescript/commonjs/features/chatbot/types/translation.types.d.ts +1 -5
- package/dist/typescript/commonjs/features/chatbot/types/translation.types.d.ts.map +1 -1
- package/dist/typescript/commonjs/lib/effector/createEntityFieldStore.d.ts +4 -9
- package/dist/typescript/commonjs/lib/effector/createEntityFieldStore.d.ts.map +1 -1
- package/dist/typescript/commonjs/shared/icons/ChatbotIcon.d.ts.map +1 -1
- package/dist/typescript/module/features/chatbot/components/Alternatives/Alternatives.d.ts +1 -1
- package/dist/typescript/module/features/chatbot/components/Alternatives/Alternatives.d.ts.map +1 -1
- package/dist/typescript/module/features/chatbot/components/ChatControls/ChatControls.d.ts +2 -6
- package/dist/typescript/module/features/chatbot/components/ChatControls/ChatControls.d.ts.map +1 -1
- package/dist/typescript/module/features/chatbot/components/ChatMessages/ChatbotMessages.d.ts +1 -1
- package/dist/typescript/module/features/chatbot/components/ChatMessages/ChatbotMessages.d.ts.map +1 -1
- package/dist/typescript/module/features/chatbot/components/Chatbot.d.ts +2 -6
- package/dist/typescript/module/features/chatbot/components/Chatbot.d.ts.map +1 -1
- package/dist/typescript/module/features/chatbot/index.d.ts +1 -1
- package/dist/typescript/module/features/chatbot/index.d.ts.map +1 -1
- package/dist/typescript/module/features/chatbot/model/{model.d.ts → ChatBotModel.d.ts} +19 -35
- package/dist/typescript/module/features/chatbot/model/ChatBotModel.d.ts.map +1 -0
- package/dist/typescript/module/features/chatbot/model/t2s.d.ts +1 -1
- package/dist/typescript/module/features/chatbot/model/t2s.d.ts.map +1 -1
- package/dist/typescript/module/features/chatbot/model/translation.d.ts +1 -1
- package/dist/typescript/module/features/chatbot/model/translation.d.ts.map +1 -1
- package/dist/typescript/module/features/chatbot/types/model.types.d.ts +8 -20
- package/dist/typescript/module/features/chatbot/types/model.types.d.ts.map +1 -1
- package/dist/typescript/module/features/chatbot/types/translation.types.d.ts +1 -5
- package/dist/typescript/module/features/chatbot/types/translation.types.d.ts.map +1 -1
- package/dist/typescript/module/lib/effector/createEntityFieldStore.d.ts +4 -9
- package/dist/typescript/module/lib/effector/createEntityFieldStore.d.ts.map +1 -1
- package/dist/typescript/module/shared/icons/ChatbotIcon.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/features/chatbot/components/Alternatives/Alternatives.tsx +7 -10
- package/src/features/chatbot/components/ChatControls/ChatControls.tsx +5 -10
- package/src/features/chatbot/components/ChatMessage/MessageLoader.tsx +2 -3
- package/src/features/chatbot/components/ChatMessage/MessageTextToSpeech.tsx +2 -1
- package/src/features/chatbot/components/ChatMessage/ReceivedMessage.tsx +3 -4
- package/src/features/chatbot/components/ChatMessages/ChatbotMessages.tsx +15 -24
- package/src/features/chatbot/components/Chatbot.tsx +6 -10
- package/src/features/chatbot/index.ts +1 -1
- package/src/features/chatbot/model/ChatBotModel.ts +222 -0
- package/src/features/chatbot/model/t2s.ts +8 -5
- package/src/features/chatbot/model/translation.ts +181 -201
- package/src/features/chatbot/types/model.types.ts +8 -14
- package/src/features/chatbot/types/translation.types.ts +10 -12
- package/src/features/feedback/model/model.ts +1 -1
- package/src/lib/effector/createEntityFieldStore.ts +9 -35
- package/src/shared/icons/ChatbotIcon.tsx +85 -500
- package/dist/commonjs/features/chatbot/model/model.js.map +0 -1
- package/dist/module/features/chatbot/model/model.js +0 -242
- package/dist/module/features/chatbot/model/model.js.map +0 -1
- package/dist/typescript/commonjs/features/chatbot/model/model.d.ts.map +0 -1
- package/dist/typescript/module/features/chatbot/model/model.d.ts.map +0 -1
- 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
|
-
|
|
25
|
-
|
|
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 './
|
|
2
|
+
import { ChatbotModel } from './ChatBotModel'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
14
|
+
private readonly model: ChatbotModel
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
readonly setIsTranslated = createEvent<boolean>()
|
|
17
|
+
readonly $isTranslated = restore(this.setIsTranslated, false)
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
26
|
+
constructor(chatbotModel: ChatbotModel) {
|
|
27
|
+
this.model = chatbotModel
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
this.translateAlternativesFx = this.createTranslateAlternativesEffect()
|
|
30
|
+
this.translateMessageFx = this.createTranslateMessageEffect()
|
|
31
|
+
this.translateBatchFx = this.createTranslateBatchEffect()
|
|
32
32
|
|
|
33
|
-
|
|
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
|
-
|
|
36
|
+
private shouldSkipTranslation(messages: ConversationMessage[]) {
|
|
37
|
+
if (this.$isTranslated.getState()) {
|
|
38
|
+
this.setIsTranslated(false)
|
|
39
|
+
return true
|
|
49
40
|
}
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
149
|
+
const messagesText = messages.map(({ message }) => message)
|
|
217
150
|
const response = await this.model.api.translateTextFx({
|
|
218
|
-
|
|
219
|
-
|
|
151
|
+
text: messagesText,
|
|
152
|
+
targetLanguage: params.targetLanguage,
|
|
220
153
|
})
|
|
221
154
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
155
|
+
const lastMessage = messages.at(-1)
|
|
156
|
+
const lastMessageTranslatedOptions = lastMessage
|
|
157
|
+
? await this.translateMessageOptions(lastMessage, params.targetLanguage)
|
|
158
|
+
: undefined
|
|
227
159
|
|
|
228
|
-
|
|
229
|
-
|
|
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
|
}
|