@windward/integrations 0.16.0 → 0.17.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/CHANGELOG.md +12 -0
- package/components/Integration/AiAgentIntegration/ChatWindow.vue +916 -0
- package/components/LLM/BloomTaxonomySelector.vue +120 -0
- package/components/LLM/ContentSelector.vue +66 -0
- package/components/LLM/GenerateContent/AssessmentQuestionGenerateButton.vue +285 -0
- package/components/LLM/GenerateContent/BlockQuestionGenerateButton.vue +514 -0
- package/components/LLM/GenerateContent/FakeTextStream.vue +67 -0
- package/i18n/en-US/components/ai_agent/chat.ts +20 -0
- package/i18n/en-US/components/ai_agent/index.ts +5 -0
- package/i18n/en-US/components/index.ts +4 -0
- package/i18n/en-US/components/llm/blooms.ts +15 -0
- package/i18n/en-US/components/llm/content_selector.ts +3 -0
- package/i18n/en-US/components/llm/generate_content/fake_text_stream.ts +62 -0
- package/i18n/en-US/components/llm/generate_content/generate_questions.ts +81 -0
- package/i18n/en-US/components/llm/generate_content/index.ts +7 -0
- package/i18n/en-US/components/llm/index.ts +10 -0
- package/i18n/en-US/shared/permission.ts +10 -0
- package/i18n/en-US/shared/settings.ts +2 -1
- package/i18n/es-ES/components/ai_agent/chat.ts +20 -0
- package/i18n/es-ES/components/ai_agent/index.ts +5 -0
- package/i18n/es-ES/components/index.ts +4 -0
- package/i18n/es-ES/components/llm/blooms.ts +15 -0
- package/i18n/es-ES/components/llm/content_selector.ts +3 -0
- package/i18n/es-ES/components/llm/generate_content/fake_text_stream.ts +62 -0
- package/i18n/es-ES/components/llm/generate_content/generate_questions.ts +85 -0
- package/i18n/es-ES/components/llm/generate_content/index.ts +7 -0
- package/i18n/es-ES/components/llm/index.ts +10 -0
- package/i18n/es-ES/shared/permission.ts +10 -0
- package/i18n/es-ES/shared/settings.ts +2 -1
- package/i18n/sv-SE/components/ai_agent/chat.ts +19 -0
- package/i18n/sv-SE/components/ai_agent/index.ts +5 -0
- package/i18n/sv-SE/components/index.ts +4 -0
- package/i18n/sv-SE/components/llm/blooms.ts +15 -0
- package/i18n/sv-SE/components/llm/content_selector.ts +3 -0
- package/i18n/sv-SE/components/llm/generate_content/fake_text_stream.ts +62 -0
- package/i18n/sv-SE/components/llm/generate_content/generate_questions.ts +82 -0
- package/i18n/sv-SE/components/llm/generate_content/index.ts +7 -0
- package/i18n/sv-SE/components/llm/index.ts +10 -0
- package/i18n/sv-SE/shared/permission.ts +10 -0
- package/i18n/sv-SE/shared/settings.ts +1 -0
- package/models/Activity.ts +8 -0
- package/models/AgentChat.ts +12 -0
- package/models/AgentChatMessage.ts +12 -0
- package/package.json +2 -1
- package/plugin.js +34 -1
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-row class="container-generate-ai mt-2">
|
|
4
|
+
<v-col>{{
|
|
5
|
+
$t('windward.integrations.components.llm.ai_assistance')
|
|
6
|
+
}}</v-col>
|
|
7
|
+
<v-col>
|
|
8
|
+
<ContentSelector
|
|
9
|
+
v-model="selectedContent"
|
|
10
|
+
:disabled="isLoading || disabled"
|
|
11
|
+
></ContentSelector>
|
|
12
|
+
</v-col>
|
|
13
|
+
<v-col>
|
|
14
|
+
<BloomTaxonomySelector
|
|
15
|
+
v-model="selectedDifficulty"
|
|
16
|
+
:levels="taxonomyLevels"
|
|
17
|
+
:advanced-levels="isMultipleChoiceType ? false : hasAdvancedBlooms"
|
|
18
|
+
:disabled="isLoading || disabled"
|
|
19
|
+
:hide-details="!isBucketGameType"
|
|
20
|
+
></BloomTaxonomySelector>
|
|
21
|
+
|
|
22
|
+
<div v-if="true || isBucketGameType" class="text-caption mt-1">
|
|
23
|
+
{{
|
|
24
|
+
$t(
|
|
25
|
+
'windward.integrations.components.llm.generate_content.generate_questions.replaces_content'
|
|
26
|
+
)
|
|
27
|
+
}}
|
|
28
|
+
</div>
|
|
29
|
+
</v-col>
|
|
30
|
+
<v-col
|
|
31
|
+
v-if="showReplaceExistingToggle"
|
|
32
|
+
cols="auto"
|
|
33
|
+
class="d-flex align-center"
|
|
34
|
+
>
|
|
35
|
+
<v-switch
|
|
36
|
+
v-model="replaceExisting"
|
|
37
|
+
hide-details
|
|
38
|
+
dense
|
|
39
|
+
color="secondary"
|
|
40
|
+
class="mt-0 pt-0"
|
|
41
|
+
:label="replaceExistingLabel"
|
|
42
|
+
:disabled="isLoading"
|
|
43
|
+
></v-switch>
|
|
44
|
+
</v-col>
|
|
45
|
+
<v-col>
|
|
46
|
+
<v-btn
|
|
47
|
+
elevation="0"
|
|
48
|
+
color="secondary"
|
|
49
|
+
class="mb-1 btn-selector"
|
|
50
|
+
:loading="isLoading"
|
|
51
|
+
:disabled="isLoading"
|
|
52
|
+
@click="generateAIQuestion"
|
|
53
|
+
>
|
|
54
|
+
<v-icon v-if="!isLoading" class="pr-1">
|
|
55
|
+
mdi-magic-staff
|
|
56
|
+
</v-icon>
|
|
57
|
+
<span v-if="!isLoading">{{ buttonLabel }}</span>
|
|
58
|
+
|
|
59
|
+
<template #loader>
|
|
60
|
+
<v-progress-circular
|
|
61
|
+
v-if="!showText || !showLoadingText"
|
|
62
|
+
indeterminate
|
|
63
|
+
size="23"
|
|
64
|
+
></v-progress-circular>
|
|
65
|
+
|
|
66
|
+
<FakeTextStream
|
|
67
|
+
v-if="showText && showLoadingText"
|
|
68
|
+
></FakeTextStream>
|
|
69
|
+
</template>
|
|
70
|
+
</v-btn>
|
|
71
|
+
</v-col>
|
|
72
|
+
</v-row>
|
|
73
|
+
</div>
|
|
74
|
+
</template>
|
|
75
|
+
|
|
76
|
+
<script>
|
|
77
|
+
import _ from 'lodash'
|
|
78
|
+
import { mapGetters } from 'vuex'
|
|
79
|
+
import ContentSelector from '../ContentSelector.vue'
|
|
80
|
+
import BloomTaxonomySelector from '../BloomTaxonomySelector.vue'
|
|
81
|
+
import FakeTextStream from './FakeTextStream'
|
|
82
|
+
import Course from '~/models/Course'
|
|
83
|
+
import Content from '~/models/Content'
|
|
84
|
+
import ContentBlock from '~/models/ContentBlock'
|
|
85
|
+
|
|
86
|
+
export default {
|
|
87
|
+
name: 'BlockQuestionGenerateButton',
|
|
88
|
+
components: { ContentSelector, BloomTaxonomySelector, FakeTextStream },
|
|
89
|
+
props: {
|
|
90
|
+
// The assessment question
|
|
91
|
+
value: { type: [ContentBlock, Object], required: true },
|
|
92
|
+
course: { type: [Course, Object], required: true },
|
|
93
|
+
content: { type: [Content, Object], required: true },
|
|
94
|
+
disabled: { type: Boolean, required: false, default: false },
|
|
95
|
+
showText: { type: Boolean, required: false, default: true },
|
|
96
|
+
},
|
|
97
|
+
data() {
|
|
98
|
+
return {
|
|
99
|
+
loadingTimeout: null,
|
|
100
|
+
isLoading: false,
|
|
101
|
+
showLoadingText: false,
|
|
102
|
+
selectedContent: null,
|
|
103
|
+
selectedDifficulty: null,
|
|
104
|
+
replaceExisting:
|
|
105
|
+
this.value.tag === 'plugin-games-bucket' ||
|
|
106
|
+
this.value.tag === 'plugin-games-wordjumble-game' ||
|
|
107
|
+
this.value.tag === 'plugin-games-multiple-choice' ||
|
|
108
|
+
this.value.tag === 'plugin-games-seven-strikes-game',
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
computed: {
|
|
112
|
+
...mapGetters({
|
|
113
|
+
contentTree: 'content/getTree',
|
|
114
|
+
}),
|
|
115
|
+
showReplaceExistingToggle() {
|
|
116
|
+
return (
|
|
117
|
+
this.isFlashcardType ||
|
|
118
|
+
this.isMatchingGameType ||
|
|
119
|
+
this.isSortingGameType ||
|
|
120
|
+
this.isWordJumbleType ||
|
|
121
|
+
this.isMultipleChoiceType ||
|
|
122
|
+
this.isSevenStrikesType
|
|
123
|
+
)
|
|
124
|
+
},
|
|
125
|
+
isFlashcardType() {
|
|
126
|
+
return this.value.tag === 'plugin-games-flash-card'
|
|
127
|
+
},
|
|
128
|
+
isBucketGameType() {
|
|
129
|
+
return this.value.tag === 'plugin-games-bucket'
|
|
130
|
+
},
|
|
131
|
+
isMatchingGameType() {
|
|
132
|
+
return this.value.tag === 'plugin-games-matching-game'
|
|
133
|
+
},
|
|
134
|
+
isSortingGameType() {
|
|
135
|
+
return this.value.tag === 'plugin-games-sorting'
|
|
136
|
+
},
|
|
137
|
+
isScenarioGameType() {
|
|
138
|
+
return this.value.tag === 'plugin-core-scenario-choice'
|
|
139
|
+
},
|
|
140
|
+
isSevenStrikesType() {
|
|
141
|
+
return (
|
|
142
|
+
this.value.tag === 'plugin-games-seven-strikes-game'
|
|
143
|
+
)
|
|
144
|
+
},
|
|
145
|
+
isWordJumbleType() {
|
|
146
|
+
return this.value.tag === 'plugin-games-wordjumble-game'
|
|
147
|
+
},
|
|
148
|
+
isMultipleChoiceType() {
|
|
149
|
+
return this.value.tag === 'plugin-games-multiple-choice'
|
|
150
|
+
},
|
|
151
|
+
replaceExistingLabel() {
|
|
152
|
+
if (this.isBucketGameType) {
|
|
153
|
+
return this.$t(
|
|
154
|
+
'windward.integrations.components.llm.generate_content.generate_questions.replace_existing_bucket_game'
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
if (this.isMatchingGameType) {
|
|
158
|
+
return this.$t(
|
|
159
|
+
'windward.integrations.components.llm.generate_content.generate_questions.replace_existing_matching_game'
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
if (this.isSortingGameType) {
|
|
163
|
+
return this.$t(
|
|
164
|
+
'windward.integrations.components.llm.generate_content.generate_questions.replace_existing_sorting_game'
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
if (this.isWordJumbleType) {
|
|
168
|
+
return this.$t(
|
|
169
|
+
'windward.integrations.components.llm.generate_content.generate_questions.replace_existing_word_jumble'
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
if (this.isMultipleChoiceType) {
|
|
173
|
+
return this.$t(
|
|
174
|
+
'windward.integrations.components.llm.generate_content.generate_questions.replace_existing_multiple_choice'
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
if (this.isFlashcardType) {
|
|
178
|
+
return this.$t(
|
|
179
|
+
'windward.integrations.components.llm.generate_content.generate_questions.replace_existing_flashcard'
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
return this.$t(
|
|
183
|
+
'windward.integrations.components.llm.generate_content.generate_questions.replace_existing'
|
|
184
|
+
)
|
|
185
|
+
},
|
|
186
|
+
hasAdvancedBlooms() {
|
|
187
|
+
const supportsAdvancedTaxonomy =
|
|
188
|
+
this.isSortingGameType ||
|
|
189
|
+
this.isScenarioGameType ||
|
|
190
|
+
(!this.isFlashcardType &&
|
|
191
|
+
!this.isBucketGameType &&
|
|
192
|
+
!this.isMatchingGameType &&
|
|
193
|
+
!this.isSevenStrikesType)
|
|
194
|
+
|
|
195
|
+
return supportsAdvancedTaxonomy
|
|
196
|
+
},
|
|
197
|
+
taxonomyLevels() {
|
|
198
|
+
// For word jumble games, only show None, Remember, Understand, Apply
|
|
199
|
+
if (this.isWordJumbleType) {
|
|
200
|
+
return ['None', 'Remember', 'Understand', 'Apply']
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// For multiple choice games, only show None, Remember, Understand, Apply
|
|
204
|
+
if (this.isMultipleChoiceType) {
|
|
205
|
+
return ['None', 'Remember', 'Understand', 'Apply']
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// For sorting games, only show None, Remember, Understand, Apply
|
|
209
|
+
if (this.isSortingGameType) {
|
|
210
|
+
return ['None', 'Remember', 'Understand', 'Apply']
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// For seven strikes games, only show None, Remember, Understand, Apply
|
|
214
|
+
if (this.isSevenStrikesType) {
|
|
215
|
+
return ['None', 'Remember', 'Understand', 'Apply']
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// For scenario games, only show None, Apply, Analyze, Evaluate
|
|
219
|
+
if (this.isScenarioGameType) {
|
|
220
|
+
return ['None', 'Apply', 'Analyze', 'Evaluate']
|
|
221
|
+
}
|
|
222
|
+
return []
|
|
223
|
+
},
|
|
224
|
+
buttonLabel() {
|
|
225
|
+
if (this.value.tag === 'plugin-games-flash-card') {
|
|
226
|
+
return this.$t(
|
|
227
|
+
'windward.integrations.components.llm.generate_content.generate_questions.button_label_flashcard'
|
|
228
|
+
)
|
|
229
|
+
} else if (this.value.tag === 'plugin-games-bucket') {
|
|
230
|
+
return this.$t(
|
|
231
|
+
'windward.integrations.components.llm.generate_content.generate_questions.button_label_bucket_game'
|
|
232
|
+
)
|
|
233
|
+
} else if (this.value.tag === 'plugin-games-matching-game') {
|
|
234
|
+
return this.$t(
|
|
235
|
+
'windward.integrations.components.llm.generate_content.generate_questions.button_label_matching_game'
|
|
236
|
+
)
|
|
237
|
+
} else if (this.value.tag === 'plugin-games-sorting') {
|
|
238
|
+
return this.$t(
|
|
239
|
+
'windward.integrations.components.llm.generate_content.generate_questions.button_label_sorting_game'
|
|
240
|
+
)
|
|
241
|
+
} else if (this.value.tag === 'plugin-core-scenario-choice') {
|
|
242
|
+
return this.$t(
|
|
243
|
+
'windward.integrations.components.llm.generate_content.generate_questions.button_label_scenario_game'
|
|
244
|
+
)
|
|
245
|
+
} else if (this.value.tag === 'plugin-games-wordjumble-game') {
|
|
246
|
+
return this.$t(
|
|
247
|
+
'windward.integrations.components.llm.generate_content.generate_questions.button_label_word_jumble'
|
|
248
|
+
)
|
|
249
|
+
} else if (this.value.tag === 'plugin-games-seven-strikes-game') {
|
|
250
|
+
return this.$t(
|
|
251
|
+
'windward.integrations.components.llm.generate_content.generate_questions.button_label_seven_strikes'
|
|
252
|
+
)
|
|
253
|
+
} else if (this.value.tag === 'plugin-games-multiple-choice') {
|
|
254
|
+
return this.$t(
|
|
255
|
+
'windward.integrations.components.llm.generate_content.generate_questions.button_label_multiple_choice'
|
|
256
|
+
)
|
|
257
|
+
} else {
|
|
258
|
+
return this.$t(
|
|
259
|
+
'windward.integrations.components.llm.generate_content.generate_questions.button_label'
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
methods: {
|
|
265
|
+
async generateAIQuestion() {
|
|
266
|
+
this.isLoading = true
|
|
267
|
+
// Wait 5 seconds before kicking on the text
|
|
268
|
+
this.loadingTimeout = setTimeout(() => {
|
|
269
|
+
this.showLoadingText = true
|
|
270
|
+
}, 5000)
|
|
271
|
+
try {
|
|
272
|
+
const course = new Course(this.course)
|
|
273
|
+
const content = new Content(
|
|
274
|
+
this.selectedContent || this.content
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
const responseBuilder = ContentBlock.custom(
|
|
278
|
+
course,
|
|
279
|
+
content,
|
|
280
|
+
`suggest-activities`
|
|
281
|
+
).where('block_tag', this.value.tag)
|
|
282
|
+
|
|
283
|
+
// Apply blooms level filter if it's not 'None'
|
|
284
|
+
if (this.selectedDifficulty !== 'None') {
|
|
285
|
+
responseBuilder.where(
|
|
286
|
+
'blooms_level',
|
|
287
|
+
this.selectedDifficulty
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const response = await responseBuilder.get()
|
|
292
|
+
|
|
293
|
+
let blockData = null
|
|
294
|
+
|
|
295
|
+
if (
|
|
296
|
+
response &&
|
|
297
|
+
response.tag &&
|
|
298
|
+
response.tag === this.value.tag
|
|
299
|
+
) {
|
|
300
|
+
blockData = response
|
|
301
|
+
} else if (
|
|
302
|
+
Array.isArray(response) &&
|
|
303
|
+
response.length > 0 &&
|
|
304
|
+
response[0] &&
|
|
305
|
+
response[0].tag &&
|
|
306
|
+
response[0].tag === this.value.tag
|
|
307
|
+
) {
|
|
308
|
+
blockData = response[0]
|
|
309
|
+
}
|
|
310
|
+
if (blockData !== null) {
|
|
311
|
+
if (this.replaceExisting) {
|
|
312
|
+
this.$emit('input', _.cloneDeep(blockData))
|
|
313
|
+
} else {
|
|
314
|
+
this.$emit('append', blockData)
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
throw new Error('Invalid response from generation')
|
|
318
|
+
}
|
|
319
|
+
} catch (error) {
|
|
320
|
+
const errorMessage =
|
|
321
|
+
error.response?.data?.error?.message ||
|
|
322
|
+
error.message ||
|
|
323
|
+
'assessment.error.technical'
|
|
324
|
+
const errorType = errorMessage.split('.').pop()
|
|
325
|
+
const basePath =
|
|
326
|
+
'windward.integrations.components.llm.generate_content.generate_questions.errors'
|
|
327
|
+
|
|
328
|
+
let errorText = ''
|
|
329
|
+
|
|
330
|
+
// Check for character limit error by structured data first
|
|
331
|
+
const errorSubtype =
|
|
332
|
+
error.response?.data?.error?.details?.error_subtype
|
|
333
|
+
const errorDetails = error.response?.data?.error?.details
|
|
334
|
+
|
|
335
|
+
if (errorSubtype === 'CHARACTER_LIMIT_EXCEEDED') {
|
|
336
|
+
// Use structured data if available
|
|
337
|
+
const field = errorDetails?.field || 'content'
|
|
338
|
+
const limit = errorDetails?.limit
|
|
339
|
+
const actual = errorDetails?.actual
|
|
340
|
+
|
|
341
|
+
// Use parameterized message if we have the data
|
|
342
|
+
if (limit && actual) {
|
|
343
|
+
errorText = this.$t(
|
|
344
|
+
`${basePath}.character_limit_detailed`,
|
|
345
|
+
{ field, limit, actual }
|
|
346
|
+
)
|
|
347
|
+
} else {
|
|
348
|
+
// Fallback to generic message
|
|
349
|
+
errorText = this.$t(`${basePath}.character_limit`)
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Keep backward compatibility - check for old message pattern
|
|
353
|
+
else if (
|
|
354
|
+
errorDetails?.message ===
|
|
355
|
+
'An error occurred while generating content. Try generating again.'
|
|
356
|
+
) {
|
|
357
|
+
errorText = this.$t(`${basePath}.character_limit`)
|
|
358
|
+
} else if (
|
|
359
|
+
// Check for content mismatch error specifically for bucket games
|
|
360
|
+
(errorMessage === 'activity.error.content_mismatch' ||
|
|
361
|
+
errorType === 'content_mismatch') &&
|
|
362
|
+
this.value.tag === 'plugin-games-bucket'
|
|
363
|
+
) {
|
|
364
|
+
errorText =
|
|
365
|
+
this.$t(`${basePath}.content_mismatch_bucket_game`) +
|
|
366
|
+
'\n\n' +
|
|
367
|
+
this.$t(
|
|
368
|
+
`${basePath}.content_mismatch_bucket_game_support`
|
|
369
|
+
)
|
|
370
|
+
} else if (
|
|
371
|
+
(errorMessage === 'activity.error.content_mismatch' ||
|
|
372
|
+
errorType === 'content_mismatch') &&
|
|
373
|
+
this.value.tag === 'plugin-games-multiple-choice'
|
|
374
|
+
) {
|
|
375
|
+
errorText =
|
|
376
|
+
this.$t(`${basePath}.content_mismatch_multiple_choice`) +
|
|
377
|
+
'\n\n' +
|
|
378
|
+
this.$t(
|
|
379
|
+
`${basePath}.content_mismatch_multiple_choice_support`
|
|
380
|
+
)
|
|
381
|
+
} else if (
|
|
382
|
+
(errorMessage === 'activity.error.content_mismatch' ||
|
|
383
|
+
errorType === 'content_mismatch') &&
|
|
384
|
+
this.value.tag === 'plugin-games-matching-game'
|
|
385
|
+
) {
|
|
386
|
+
errorText =
|
|
387
|
+
this.$t(`${basePath}.content_mismatch_matching_game`) +
|
|
388
|
+
'\n\n' +
|
|
389
|
+
this.$t(
|
|
390
|
+
`${basePath}.content_mismatch_matching_game_support`
|
|
391
|
+
)
|
|
392
|
+
} else if (
|
|
393
|
+
(errorMessage === 'activity.error.content_mismatch' ||
|
|
394
|
+
errorType === 'content_mismatch') &&
|
|
395
|
+
this.value.tag === 'plugin-games-sorting'
|
|
396
|
+
) {
|
|
397
|
+
errorText =
|
|
398
|
+
this.$t(`${basePath}.content_mismatch_sorting_game`) +
|
|
399
|
+
'\n\n' +
|
|
400
|
+
this.$t(
|
|
401
|
+
`${basePath}.content_mismatch_sorting_game_support`
|
|
402
|
+
)
|
|
403
|
+
} else if (
|
|
404
|
+
(errorMessage === 'activity.error.content_mismatch' ||
|
|
405
|
+
errorType === 'content_mismatch') &&
|
|
406
|
+
this.value.tag === 'plugin-core-scenario-choice'
|
|
407
|
+
) {
|
|
408
|
+
errorText =
|
|
409
|
+
this.$t(`${basePath}.content_mismatch_scenario_game`) +
|
|
410
|
+
'\n\n' +
|
|
411
|
+
this.$t(
|
|
412
|
+
`${basePath}.content_mismatch_scenario_game_support`
|
|
413
|
+
)
|
|
414
|
+
} else if (
|
|
415
|
+
(errorMessage === 'activity.error.content_mismatch' ||
|
|
416
|
+
errorType === 'content_mismatch') &&
|
|
417
|
+
this.value.tag === 'plugin-games-wordjumble-game'
|
|
418
|
+
) {
|
|
419
|
+
errorText =
|
|
420
|
+
this.$t(`${basePath}.content_mismatch_word_jumble`) +
|
|
421
|
+
'\n\n' +
|
|
422
|
+
this.$t(
|
|
423
|
+
`${basePath}.content_mismatch_word_jumble_support`
|
|
424
|
+
)
|
|
425
|
+
} else if (
|
|
426
|
+
errorType === 'insufficient_content' &&
|
|
427
|
+
error.response?.data?.error?.details
|
|
428
|
+
) {
|
|
429
|
+
// Handle dynamic Bloom's taxonomy insufficient content errors
|
|
430
|
+
const details = error.response.data.error.details
|
|
431
|
+
const bloomsLevel = details.blooms_level
|
|
432
|
+
const minimumRequired = details.minimum_required
|
|
433
|
+
|
|
434
|
+
if (bloomsLevel && bloomsLevel !== 'None') {
|
|
435
|
+
// Message with Bloom's level
|
|
436
|
+
errorText =
|
|
437
|
+
this.$t(`${basePath}.insufficient_content_blooms`, {
|
|
438
|
+
bloomsLevel,
|
|
439
|
+
}) +
|
|
440
|
+
'\n\n' +
|
|
441
|
+
this.$t(
|
|
442
|
+
`${basePath}.insufficient_content_blooms_support`,
|
|
443
|
+
{ minimumRequired }
|
|
444
|
+
)
|
|
445
|
+
} else if (minimumRequired) {
|
|
446
|
+
// Message without Bloom's level but with word count
|
|
447
|
+
errorText =
|
|
448
|
+
this.$t(
|
|
449
|
+
`${basePath}.insufficient_content_dynamic`
|
|
450
|
+
) +
|
|
451
|
+
'\n\n' +
|
|
452
|
+
this.$t(
|
|
453
|
+
`${basePath}.insufficient_content_dynamic_support`,
|
|
454
|
+
{ minimumRequired }
|
|
455
|
+
)
|
|
456
|
+
} else {
|
|
457
|
+
// Fallback to static translation
|
|
458
|
+
errorText =
|
|
459
|
+
this.$t(`${basePath}.${errorType}`) +
|
|
460
|
+
'\n\n' +
|
|
461
|
+
this.$t(`${basePath}.${errorType}_support`)
|
|
462
|
+
}
|
|
463
|
+
} else {
|
|
464
|
+
// Check if the error type is a valid i18n key
|
|
465
|
+
const errorKey = `${basePath}.${errorType}`
|
|
466
|
+
const supportKey = `${basePath}.${errorType}_support`
|
|
467
|
+
|
|
468
|
+
// Try to get the translation, fall back to default error if not found
|
|
469
|
+
const hasTranslation = this.$te(errorKey)
|
|
470
|
+
|
|
471
|
+
if (hasTranslation) {
|
|
472
|
+
errorText =
|
|
473
|
+
this.$t(errorKey) + '\n\n' + this.$t(supportKey)
|
|
474
|
+
} else {
|
|
475
|
+
// Fall back to default error message
|
|
476
|
+
errorText =
|
|
477
|
+
this.$t(`${basePath}.default`) +
|
|
478
|
+
'\n\n' +
|
|
479
|
+
this.$t(`${basePath}.default_support`)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (errorType === 'technical') {
|
|
483
|
+
const errorCode =
|
|
484
|
+
error.response?.data?.error?.details?.error_type ||
|
|
485
|
+
'UNKNOWN'
|
|
486
|
+
errorText = errorText.replace('[ERROR_CODE]', errorCode)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
this.$dialog.error(errorText, {
|
|
491
|
+
duration: 5000,
|
|
492
|
+
keepOnHover: true,
|
|
493
|
+
singleton: true,
|
|
494
|
+
type: 'error',
|
|
495
|
+
})
|
|
496
|
+
} finally {
|
|
497
|
+
this.isLoading = false
|
|
498
|
+
this.showLoadingText = false
|
|
499
|
+
clearTimeout(this.loadingTimeout)
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
}
|
|
504
|
+
</script>
|
|
505
|
+
|
|
506
|
+
<style lang="scss" scoped>
|
|
507
|
+
.btn-selector {
|
|
508
|
+
width: 100%;
|
|
509
|
+
}
|
|
510
|
+
.container-generate-ai {
|
|
511
|
+
outline: 1px solid var(--v-secondary-base);
|
|
512
|
+
border-radius: $border-radius-root;
|
|
513
|
+
}
|
|
514
|
+
</style>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-scroll-y-transition>
|
|
3
|
+
<div v-if="showText" class="quote">{{ activeQuote }}</div>
|
|
4
|
+
</v-scroll-y-transition>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
import _ from 'lodash'
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
name: 'FakeTextStream',
|
|
12
|
+
data() {
|
|
13
|
+
return {
|
|
14
|
+
interval: null,
|
|
15
|
+
timeout: null,
|
|
16
|
+
intervalDelay: 2500,
|
|
17
|
+
transitionDelay: 500,
|
|
18
|
+
activeQuote: '',
|
|
19
|
+
showText: false,
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
computed: {
|
|
23
|
+
quotes() {
|
|
24
|
+
const localizedQuotes = this.$t(
|
|
25
|
+
'windward.integrations.components.llm.generate_content.fake_text_stream.options'
|
|
26
|
+
)
|
|
27
|
+
const quotes = []
|
|
28
|
+
|
|
29
|
+
for (const [_key, quote] of Object.entries(localizedQuotes)) {
|
|
30
|
+
quotes.push(quote)
|
|
31
|
+
}
|
|
32
|
+
return quotes
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
mounted() {
|
|
36
|
+
this.setQuote()
|
|
37
|
+
this.interval = setInterval(() => {
|
|
38
|
+
this.setQuote()
|
|
39
|
+
}, this.intervalDelay)
|
|
40
|
+
},
|
|
41
|
+
beforeDestroy() {
|
|
42
|
+
clearInterval(this.interval)
|
|
43
|
+
clearTimeout(this.timeout)
|
|
44
|
+
},
|
|
45
|
+
methods: {
|
|
46
|
+
setQuote() {
|
|
47
|
+
// Hide the old quote
|
|
48
|
+
this.showText = false
|
|
49
|
+
|
|
50
|
+
// Wait
|
|
51
|
+
this.timeout = setTimeout(() => {
|
|
52
|
+
this.showText = true
|
|
53
|
+
const index = Math.floor(Math.random() * this.quotes.length)
|
|
54
|
+
this.activeQuote = this.quotes[index]
|
|
55
|
+
}, this.transitionDelay)
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<style scoped>
|
|
62
|
+
.quote {
|
|
63
|
+
max-width: initial;
|
|
64
|
+
overflow: hidden;
|
|
65
|
+
text-overflow: ellipsis;
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
new_chat: 'New Chat',
|
|
3
|
+
recent_chats: 'Recent chats',
|
|
4
|
+
untitled_chat: 'Untitled chat',
|
|
5
|
+
no_history: 'No recent chats',
|
|
6
|
+
system_tip:
|
|
7
|
+
"Hi! I'm here to help with your course. Try asking about learning objectives, content structure, or best practices.",
|
|
8
|
+
placeholder: 'Ask about your course content...',
|
|
9
|
+
hint: 'Press Enter to send, Shift+Enter for new line',
|
|
10
|
+
error_generic: 'Something went wrong. Please try again.',
|
|
11
|
+
error_with_status:
|
|
12
|
+
'Something went wrong. Please try again. (Error {status})',
|
|
13
|
+
action_try_again: 'Try again',
|
|
14
|
+
action_dismiss: 'Dismiss',
|
|
15
|
+
sender_you: 'You',
|
|
16
|
+
sender_agent: 'Assistant',
|
|
17
|
+
limit_reached:
|
|
18
|
+
'You have reached the maximum number of messages allowed in this chat. Please start a new chat to continue.',
|
|
19
|
+
aria_open_history: 'Open chat history',
|
|
20
|
+
}
|
|
@@ -4,12 +4,16 @@ import navigation from './navigation'
|
|
|
4
4
|
import integration from './integration'
|
|
5
5
|
import externalIntegration from './external_integration'
|
|
6
6
|
import fileImport from './file_import'
|
|
7
|
+
import aiAgent from './ai_agent'
|
|
8
|
+
import llm from './llm'
|
|
7
9
|
|
|
8
10
|
export default {
|
|
9
11
|
content,
|
|
10
12
|
settings,
|
|
11
13
|
navigation,
|
|
12
14
|
integration,
|
|
15
|
+
llm,
|
|
13
16
|
external_integration: externalIntegration,
|
|
14
17
|
file_import: fileImport,
|
|
18
|
+
ai_agent: aiAgent,
|
|
15
19
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
blooms_taxonomy: "Bloom's Taxonomy Level",
|
|
3
|
+
none: 'None selected',
|
|
4
|
+
remember: 'Remember',
|
|
5
|
+
understand: 'Understand',
|
|
6
|
+
apply: 'Apply',
|
|
7
|
+
analyze: 'Analyze',
|
|
8
|
+
evaluate: 'Evaluate',
|
|
9
|
+
errors: {
|
|
10
|
+
insufficient_content_blooms:
|
|
11
|
+
'Insufficient content to generate quality questions at the {bloomsLevel} level.',
|
|
12
|
+
insufficient_content_blooms_support:
|
|
13
|
+
'Please add more detailed text, examples, or explanations to this section. We recommend at least {minimumRequired} words of relevant content to generate appropriate questions at this level.',
|
|
14
|
+
},
|
|
15
|
+
}
|