@windward/core 0.22.0 → 0.24.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 +29 -1
- package/components/Content/Blocks/ClickableIcons.vue +8 -5
- package/components/Content/Blocks/Email.vue +12 -3
- package/components/Content/Blocks/OpenResponse.vue +1 -0
- package/components/Content/Blocks/Video.vue +3 -0
- package/components/Settings/AccordionSettings.vue +0 -1
- package/components/Settings/ScenarioChoiceSettings.vue +37 -22
- package/components/Settings/TabSettings.vue +0 -1
- package/components/Settings/TextEditorSettings.vue +4 -0
- package/components/Settings/VideoSettings/SourcePicker.vue +1 -1
- package/package.json +2 -2
- package/plugin.js +7 -6
- package/utils/index.js +0 -2
- package/components/Navigation/Items/GlossaryNav.vue +0 -53
- package/components/Navigation/Items/UserUploadNav.vue +0 -58
- package/components/utils/GenerateAIQuestionButton.vue +0 -826
package/CHANGELOG.md
CHANGED
|
@@ -1,20 +1,48 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## Release [0.
|
|
3
|
+
## Release [0.24.0] - 2025-10-06
|
|
4
4
|
|
|
5
|
+
* Merged in bugfix/LE-2134-email-block-show-hide-title (pull request #444)
|
|
6
|
+
* Merged in feature/LE-2097-tabs-and-accordions-hide-backgro (pull request #436)
|
|
7
|
+
* Merged release/0.24.0 into feature/LE-2097-tabs-and-accordions-hide-backgro
|
|
8
|
+
* Merged in bugfix/LE-1841-all-blocks-save-or-cancel-change (pull request #440)
|
|
9
|
+
* Merged release/0.24.0 into bugfix/LE-1841-all-blocks-save-or-cancel-change
|
|
10
|
+
* Merged in feature/LE-2100-edit-in-text-area-update-text-fi (pull request #435)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Release [0.23.0] - 2025-09-18
|
|
14
|
+
|
|
15
|
+
* Merged in feature/LE-2099-track-engagement-on-videos-from- (pull request #439)
|
|
16
|
+
* Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #438)
|
|
17
|
+
* Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #437)
|
|
18
|
+
* Merged in bugfix/LE-2088-open-response-block-remove-edito (pull request #434)
|
|
19
|
+
* Merged in bugfix/MIND-6075-decouple-generateaiquestionbut (pull request #432)
|
|
20
|
+
* Merged in bugfix/LE-2071-vimeo-videos-not-working (pull request #433)
|
|
21
|
+
* Merged in bugfix/LE-2052-clickable-icon-text-color-should (pull request #426)
|
|
22
|
+
* Merged in bugfix/LE-2088-open-response-block-remove-edito (pull request #430)
|
|
23
|
+
* Merged release/0.23.0 into bugfix/LE-2071-vimeo-videos-not-working
|
|
24
|
+
* Merged release/0.22.0 into bugfix/LE-2052-clickable-icon-text-color-should
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## Release [0.22.0] - 2025-08-28
|
|
28
|
+
|
|
29
|
+
* Merge branch 'master' into release/0.22.0
|
|
5
30
|
* Merged in bugfix/LE-2075-the-panel-of-the-text-block-isnt (pull request #427)
|
|
6
31
|
* Merged in bug-fix/LE-2060/llm-character-limit-validation (pull request #421)
|
|
7
32
|
* Merged in bugfix/LE-2031-open-response-download-collate-q (pull request #422)
|
|
8
33
|
* Merged in bugfix/LE-1960-video-using-urls-as-a-source (pull request #423)
|
|
34
|
+
* Merged in hotfix/0.21.1 (pull request #425)
|
|
9
35
|
* Merged release/0.22.0 into bugfix/LE-1960-video-using-urls-as-a-source
|
|
10
36
|
* Merged in bugfix/LE-2057-save-button-disappeared-again-on (pull request #419)
|
|
11
37
|
* Merged release/0.22.0 into bugfix/LE-1960-video-using-urls-as-a-source
|
|
12
38
|
* Merged in feature/LE-2036/word-jumble-gen (pull request #420)
|
|
13
39
|
* Merged in feature/LE-1997/scenario-gen (pull request #416)
|
|
14
40
|
* Merged in bugfix/LE-1928-user-upload-allowed-file-types (pull request #415)
|
|
41
|
+
* Merged in release/0.21.0 (pull request #401)
|
|
15
42
|
* Merged release/0.21.0 into bugfix/LE-1960-video-using-urls-as-a-source
|
|
16
43
|
* Merged release/0.21.0 into bugfix/LE-1928-user-upload-allowed-file-types
|
|
17
44
|
|
|
45
|
+
|
|
18
46
|
## Hotfix [0.21.1] created - 2025-08-20
|
|
19
47
|
|
|
20
48
|
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
</v-avatar>
|
|
55
55
|
<v-icon
|
|
56
56
|
v-else-if="isIcon(item.icon)"
|
|
57
|
-
class="clickable--icon
|
|
58
|
-
>{{ item.icon }}
|
|
59
|
-
>
|
|
60
|
-
<span v-else :class="iconClass + '
|
|
57
|
+
class="clickable--icon dark-text--text"
|
|
58
|
+
>{{ item.icon }}
|
|
59
|
+
</v-icon>
|
|
60
|
+
<span v-else :class="iconClass + ' dark-text--text'">{{
|
|
61
61
|
decode(item.icon)
|
|
62
62
|
}}</span>
|
|
63
63
|
</button>
|
|
@@ -142,7 +142,7 @@ export default {
|
|
|
142
142
|
) &&
|
|
143
143
|
this.itemColor(itemIndex)
|
|
144
144
|
) {
|
|
145
|
-
classes += ' ' + this.itemColor(itemIndex) + '
|
|
145
|
+
classes += ' ' + this.itemColor(itemIndex) + ' dark-text--text'
|
|
146
146
|
}
|
|
147
147
|
return classes
|
|
148
148
|
}
|
|
@@ -276,6 +276,9 @@ button.button-icon.button-icon--outline {
|
|
|
276
276
|
background-color: #fff !important;
|
|
277
277
|
border-width: 4px;
|
|
278
278
|
border-style: solid;
|
|
279
|
+
i.clickable--icon.dark-text--text {
|
|
280
|
+
color: var(--v-dark-text-base) !important;
|
|
281
|
+
}
|
|
279
282
|
}
|
|
280
283
|
|
|
281
284
|
button.button-icon.button-icon--rounded {
|
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
"
|
|
8
8
|
>
|
|
9
9
|
<v-col cols="12" class="pa-0">
|
|
10
|
-
<h2
|
|
10
|
+
<h2
|
|
11
|
+
v-if="
|
|
12
|
+
block.metadata.config.title &&
|
|
13
|
+
block.metadata.config.display_title
|
|
14
|
+
"
|
|
15
|
+
tabindex="0"
|
|
16
|
+
>
|
|
11
17
|
{{ block.metadata.config.title }}
|
|
12
18
|
</h2>
|
|
13
19
|
<p
|
|
@@ -141,7 +147,10 @@
|
|
|
141
147
|
)
|
|
142
148
|
}}: {{ item.cc }}
|
|
143
149
|
</div>
|
|
144
|
-
<div
|
|
150
|
+
<div
|
|
151
|
+
v-if="item.subject"
|
|
152
|
+
class="div-details-subject"
|
|
153
|
+
>
|
|
145
154
|
{{
|
|
146
155
|
$t(
|
|
147
156
|
'windward.core.components.content.blocks.email.subject'
|
|
@@ -317,7 +326,7 @@ export default {
|
|
|
317
326
|
methods: {
|
|
318
327
|
onRemoveTags(body) {
|
|
319
328
|
if (typeof body !== 'string') {
|
|
320
|
-
|
|
329
|
+
return ''
|
|
321
330
|
}
|
|
322
331
|
let text = body.replace(/ /g, ' ')
|
|
323
332
|
text = text.replace(/\u00A0/g, ' ')
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
v-model="response"
|
|
34
34
|
:height="200"
|
|
35
35
|
menubar=""
|
|
36
|
+
:toolbar="'styles | bold italic underline strikethrough removeformat | alignleft aligncenter alignright | table tablerowprops tablecellprops |bullist numlist outdent indent | a11yButton | undo redo'"
|
|
36
37
|
></TextEditor>
|
|
37
38
|
<p class="pa-3 text-center">
|
|
38
39
|
<v-btn
|
|
@@ -390,6 +390,9 @@ export default {
|
|
|
390
390
|
this.emitCompleted()
|
|
391
391
|
}
|
|
392
392
|
}
|
|
393
|
+
|
|
394
|
+
// Manually emit `hook:updated` on timeupdates since otherwise the DOM isn't actually changing so the state never gets preserved
|
|
395
|
+
this.$emit('hook:updated')
|
|
393
396
|
},
|
|
394
397
|
async onBeforeSave() {
|
|
395
398
|
this.block.body = 'video'
|
|
@@ -129,17 +129,21 @@
|
|
|
129
129
|
</v-btn>
|
|
130
130
|
</v-row>
|
|
131
131
|
</v-container>
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
<v-container class="pa-4 mb-6">
|
|
134
134
|
<v-row>
|
|
135
135
|
<v-col cols="12">
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
<PluginRef
|
|
137
|
+
target="contentBlockSettingTool"
|
|
138
|
+
:attrs="{
|
|
139
|
+
value: block,
|
|
140
|
+
course: course,
|
|
141
|
+
content: currentContent,
|
|
142
|
+
}"
|
|
143
|
+
:on="{
|
|
144
|
+
append: onPluginAppendBlock,
|
|
145
|
+
}"
|
|
146
|
+
></PluginRef>
|
|
143
147
|
</v-col>
|
|
144
148
|
</v-row>
|
|
145
149
|
</v-container>
|
|
@@ -203,14 +207,14 @@
|
|
|
203
207
|
</template>
|
|
204
208
|
<script>
|
|
205
209
|
import _ from 'lodash'
|
|
206
|
-
import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
|
|
207
210
|
import { mapGetters } from 'vuex'
|
|
211
|
+
import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
|
|
208
212
|
import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
|
|
209
213
|
import SortableExpansionPanel from '~/components/Core/SortableExpansionPanel.vue'
|
|
210
214
|
import DialogBox from '~/components/Core/DialogBox.vue'
|
|
211
215
|
import Uuid from '~/helpers/Uuid'
|
|
212
216
|
import TextEditor from '~/components/Text/TextEditor'
|
|
213
|
-
import
|
|
217
|
+
import PluginRef from '~/components/Core/PluginRef.vue'
|
|
214
218
|
|
|
215
219
|
export default {
|
|
216
220
|
name: 'ScenarioChoiceSettings',
|
|
@@ -219,7 +223,7 @@ export default {
|
|
|
219
223
|
TextEditor,
|
|
220
224
|
DialogBox,
|
|
221
225
|
BaseContentBlockSettings,
|
|
222
|
-
|
|
226
|
+
PluginRef,
|
|
223
227
|
},
|
|
224
228
|
extends: BaseContentSettings,
|
|
225
229
|
data() {
|
|
@@ -369,41 +373,52 @@ export default {
|
|
|
369
373
|
!_.isEmpty(this.block.metadata.config.link_content_id) &&
|
|
370
374
|
!_.isEmpty(this.block.metadata.config.link_text)
|
|
371
375
|
},
|
|
372
|
-
|
|
376
|
+
onPluginAppendBlock(activityData) {
|
|
373
377
|
// Process the activity data
|
|
374
|
-
if (
|
|
378
|
+
if (
|
|
379
|
+
activityData &&
|
|
380
|
+
activityData.metadata &&
|
|
375
381
|
activityData.metadata.config &&
|
|
376
382
|
activityData.metadata.config.items &&
|
|
377
|
-
Array.isArray(activityData.metadata.config.items)
|
|
378
|
-
|
|
383
|
+
Array.isArray(activityData.metadata.config.items)
|
|
384
|
+
) {
|
|
379
385
|
// Clear existing items and replace with generated ones
|
|
380
|
-
this.block.metadata.config.items.splice(
|
|
386
|
+
this.block.metadata.config.items.splice(
|
|
387
|
+
0,
|
|
388
|
+
this.block.metadata.config.items.length
|
|
389
|
+
)
|
|
381
390
|
|
|
382
391
|
// Add all new choices
|
|
383
|
-
activityData.metadata.config.items.forEach(item => {
|
|
392
|
+
activityData.metadata.config.items.forEach((item) => {
|
|
384
393
|
this.block.metadata.config.items.push({
|
|
385
394
|
title: item.title || '',
|
|
386
395
|
body: item.body || '<p></p>',
|
|
387
|
-
correct: item.correct || false
|
|
396
|
+
correct: item.correct || false,
|
|
388
397
|
})
|
|
389
398
|
})
|
|
390
399
|
|
|
391
400
|
// Update title and instructions if provided
|
|
392
401
|
if (activityData.metadata.config.title) {
|
|
393
|
-
this.block.metadata.config.title =
|
|
402
|
+
this.block.metadata.config.title =
|
|
403
|
+
activityData.metadata.config.title
|
|
394
404
|
}
|
|
395
405
|
|
|
396
406
|
if (activityData.metadata.config.instructions) {
|
|
397
|
-
this.block.metadata.config.instructions =
|
|
407
|
+
this.block.metadata.config.instructions =
|
|
408
|
+
activityData.metadata.config.instructions
|
|
398
409
|
}
|
|
399
410
|
|
|
400
411
|
this.$toast.success(
|
|
401
|
-
this.$t(
|
|
412
|
+
this.$t(
|
|
413
|
+
'windward.core.components.settings.scenario_choice.generated_successfully'
|
|
414
|
+
),
|
|
402
415
|
{ duration: 3000 }
|
|
403
416
|
)
|
|
404
417
|
} else {
|
|
405
418
|
this.$toast.error(
|
|
406
|
-
this.$t(
|
|
419
|
+
this.$t(
|
|
420
|
+
'windward.core.components.settings.scenario_choice.invalid_response'
|
|
421
|
+
),
|
|
407
422
|
{ duration: 5000 }
|
|
408
423
|
)
|
|
409
424
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
autofill
|
|
6
6
|
:disabled="render"
|
|
7
7
|
allow-read
|
|
8
|
+
:key="updateKey"
|
|
8
9
|
root-block="p"
|
|
9
10
|
show-glossary
|
|
10
11
|
:hide-text-editor="hideTextEditor"
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
</template>
|
|
53
54
|
|
|
54
55
|
<script>
|
|
56
|
+
import Crypto from '~/helpers/Crypto'
|
|
55
57
|
import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
|
|
56
58
|
import TextEditor from '~/components/Text/TextEditor'
|
|
57
59
|
export default {
|
|
@@ -73,6 +75,7 @@ export default {
|
|
|
73
75
|
elevation: 0,
|
|
74
76
|
},
|
|
75
77
|
hideTextEditor: false,
|
|
78
|
+
updateKey: Crypto.id(),
|
|
76
79
|
}
|
|
77
80
|
},
|
|
78
81
|
mounted() {
|
|
@@ -82,6 +85,7 @@ export default {
|
|
|
82
85
|
onExpand() {
|
|
83
86
|
this.hideTextEditor = !this.hideTextEditor
|
|
84
87
|
this.block.metadata.config.expand = this.hideTextEditor
|
|
88
|
+
this.updateKey = Crypto.id()
|
|
85
89
|
},
|
|
86
90
|
},
|
|
87
91
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<ContentBlockAsset
|
|
4
4
|
:value="source"
|
|
5
5
|
:assets="assets"
|
|
6
|
-
mimes="video/mp4,video/webm,video/youtube,audio/mpeg,audio/ogg,audio/webm,audio/wav"
|
|
6
|
+
mimes="video/mp4,video/webm,video/youtube,video/vimeo,audio/mpeg,audio/ogg,audio/webm,audio/wav"
|
|
7
7
|
allow-url
|
|
8
8
|
class="mb-4"
|
|
9
9
|
:disabled="disabled"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windward/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Windward UI Core Plugins",
|
|
5
5
|
"main": "plugin.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"homepage": "https://bitbucket.org/mindedge/windward-ui-plugin-core#readme",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@mindedge/vuetify-player": "^0.
|
|
24
|
+
"@mindedge/vuetify-player": "^0.5.1",
|
|
25
25
|
"@tinymce/tinymce-vue": "^3.2.8",
|
|
26
26
|
"accessibility-scanner": "^0.0.1",
|
|
27
27
|
"eslint": "^8.11.0",
|
package/plugin.js
CHANGED
|
@@ -12,8 +12,6 @@ import Email from './components/Content/Blocks/Email'
|
|
|
12
12
|
import BlockQuote from './components/Content/Blocks/BlockQuote.vue'
|
|
13
13
|
import HorizontalRule from './components/Content/Blocks/HorizontalRule.vue'
|
|
14
14
|
|
|
15
|
-
import UserUploadNav from './components/Navigation/Items/UserUploadNav.vue'
|
|
16
|
-
|
|
17
15
|
import OpenResponse from './components/Content/Blocks/OpenResponse'
|
|
18
16
|
import OpenResponseCollate from './components/Content/Blocks/OpenResponseCollate'
|
|
19
17
|
import Image from './components/Content/Blocks/Image'
|
|
@@ -24,7 +22,6 @@ import GlossaryPage from './pages/glossary.vue'
|
|
|
24
22
|
import TinymcePlugin from './pages/plugins/tinymce/_plugin.vue'
|
|
25
23
|
|
|
26
24
|
import CourseGlossaryToolNav from './components/Navigation/Items/CourseGlossaryToolNav.vue'
|
|
27
|
-
import GlossaryNav from './components/Navigation/Items/GlossaryNav.vue'
|
|
28
25
|
import AskTheExpert from './components/Navigation/Items/AskTheExpert.vue'
|
|
29
26
|
|
|
30
27
|
// Entrypoint for npm
|
|
@@ -121,7 +118,9 @@ export default {
|
|
|
121
118
|
menu: [
|
|
122
119
|
{
|
|
123
120
|
tag: 'core-user-upload-nav',
|
|
124
|
-
|
|
121
|
+
path: '/course/{course.id}/section/{section.id}/user-uploads',
|
|
122
|
+
icon: 'mdi-cloud-upload',
|
|
123
|
+
i18n: 'windward.core.components.navigation.user_upload.title',
|
|
125
124
|
context: ['course'],
|
|
126
125
|
display: ['menu'],
|
|
127
126
|
permissions: {
|
|
@@ -132,7 +131,9 @@ export default {
|
|
|
132
131
|
},
|
|
133
132
|
{
|
|
134
133
|
tag: 'core-user-glossary-nav',
|
|
135
|
-
|
|
134
|
+
path: '/course/{course.id}/section/{section.id}/glossary',
|
|
135
|
+
icon: 'mdi-comment-text-multiple',
|
|
136
|
+
i18n: 'windward.core.shared.menu.course_glossary',
|
|
136
137
|
context: ['course'],
|
|
137
138
|
display: ['menu'],
|
|
138
139
|
permissions: {
|
|
@@ -294,7 +295,7 @@ export default {
|
|
|
294
295
|
},
|
|
295
296
|
},
|
|
296
297
|
],
|
|
297
|
-
|
|
298
|
+
contentBlockSetting: [
|
|
298
299
|
{
|
|
299
300
|
tag: 'core-open-response-settings',
|
|
300
301
|
template: OpenResponseSettings,
|
package/utils/index.js
CHANGED
|
@@ -7,7 +7,6 @@ import TinyMCEWrapper from '../components/utils/TinyMCEWrapper.vue'
|
|
|
7
7
|
import MathExpressionEditor from '../components/utils/MathExpressionEditor'
|
|
8
8
|
import CourseGlossary from '../components/utils/glossary/CourseGlossary.vue'
|
|
9
9
|
import CourseGlossaryForm from '../components/utils/glossary/CourseGlossaryForm.vue'
|
|
10
|
-
import GenerateAIQuestionButton from '../components/utils/GenerateAIQuestionButton.vue'
|
|
11
10
|
|
|
12
11
|
export {
|
|
13
12
|
MathHelper,
|
|
@@ -17,5 +16,4 @@ export {
|
|
|
17
16
|
TinyMCEWrapper,
|
|
18
17
|
CourseGlossary,
|
|
19
18
|
CourseGlossaryForm,
|
|
20
|
-
GenerateAIQuestionButton,
|
|
21
19
|
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<v-tooltip right>
|
|
3
|
-
<template #activator="{ on, attrs }">
|
|
4
|
-
<v-list-item
|
|
5
|
-
:class="color"
|
|
6
|
-
:to="
|
|
7
|
-
'/course/' +
|
|
8
|
-
course.id +
|
|
9
|
-
'/section/' +
|
|
10
|
-
enrollment.course_section_id +
|
|
11
|
-
'/glossary'
|
|
12
|
-
"
|
|
13
|
-
v-bind="attrs"
|
|
14
|
-
v-on="on"
|
|
15
|
-
>
|
|
16
|
-
<v-list-item-action>
|
|
17
|
-
<v-icon v-bind="attrs" v-on="on"
|
|
18
|
-
>mdi-comment-text-multiple</v-icon
|
|
19
|
-
>
|
|
20
|
-
</v-list-item-action>
|
|
21
|
-
<v-list-item-content>
|
|
22
|
-
<v-list-item-title
|
|
23
|
-
>{{ $t('windward.core.shared.menu.course_glossary') }}
|
|
24
|
-
</v-list-item-title>
|
|
25
|
-
</v-list-item-content>
|
|
26
|
-
</v-list-item>
|
|
27
|
-
</template>
|
|
28
|
-
<span>{{ $t('windward.core.shared.menu.course_glossary') }}</span>
|
|
29
|
-
</v-tooltip>
|
|
30
|
-
</template>
|
|
31
|
-
|
|
32
|
-
<script>
|
|
33
|
-
import { mapGetters } from 'vuex'
|
|
34
|
-
|
|
35
|
-
export default {
|
|
36
|
-
name: 'GlossaryNav',
|
|
37
|
-
middleware: ['auth'],
|
|
38
|
-
props: {
|
|
39
|
-
color: { type: String, required: false, default: '' },
|
|
40
|
-
},
|
|
41
|
-
data() {
|
|
42
|
-
return {}
|
|
43
|
-
},
|
|
44
|
-
computed: {
|
|
45
|
-
...mapGetters({
|
|
46
|
-
course: 'course/get',
|
|
47
|
-
enrollment: 'enrollment/get',
|
|
48
|
-
}),
|
|
49
|
-
},
|
|
50
|
-
}
|
|
51
|
-
</script>
|
|
52
|
-
|
|
53
|
-
<style scoped></style>
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<v-tooltip right>
|
|
3
|
-
<template #activator="{ on, attrs }">
|
|
4
|
-
<v-list-item
|
|
5
|
-
:class="color"
|
|
6
|
-
:to="
|
|
7
|
-
'/course/' +
|
|
8
|
-
course.id +
|
|
9
|
-
'/section/' +
|
|
10
|
-
enrollment.course_section_id +
|
|
11
|
-
'/user-uploads'
|
|
12
|
-
"
|
|
13
|
-
v-bind="attrs"
|
|
14
|
-
v-on="on"
|
|
15
|
-
>
|
|
16
|
-
<v-list-item-action>
|
|
17
|
-
<v-icon>mdi-cloud-upload</v-icon>
|
|
18
|
-
</v-list-item-action>
|
|
19
|
-
<v-list-item-content>
|
|
20
|
-
<v-list-item-title
|
|
21
|
-
>{{
|
|
22
|
-
$t(
|
|
23
|
-
'windward.core.components.navigation.user_upload.title'
|
|
24
|
-
)
|
|
25
|
-
}}
|
|
26
|
-
</v-list-item-title>
|
|
27
|
-
</v-list-item-content>
|
|
28
|
-
</v-list-item>
|
|
29
|
-
</template>
|
|
30
|
-
<span>{{
|
|
31
|
-
$t('windward.core.components.navigation.user_upload.title')
|
|
32
|
-
}}</span>
|
|
33
|
-
</v-tooltip>
|
|
34
|
-
</template>
|
|
35
|
-
|
|
36
|
-
<script>
|
|
37
|
-
import { mapGetters } from 'vuex'
|
|
38
|
-
|
|
39
|
-
export default {
|
|
40
|
-
components: {},
|
|
41
|
-
middleware: ['auth'],
|
|
42
|
-
props: {
|
|
43
|
-
color: { type: String, required: false, default: '' },
|
|
44
|
-
},
|
|
45
|
-
data() {
|
|
46
|
-
return {}
|
|
47
|
-
},
|
|
48
|
-
computed: {
|
|
49
|
-
...mapGetters({
|
|
50
|
-
course: 'course/get',
|
|
51
|
-
enrollment: 'enrollment/get',
|
|
52
|
-
}),
|
|
53
|
-
},
|
|
54
|
-
created() {},
|
|
55
|
-
mounted() {},
|
|
56
|
-
methods: {},
|
|
57
|
-
}
|
|
58
|
-
</script>
|
|
@@ -1,826 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<v-row class="container-generate-ai mt-2">
|
|
4
|
-
<v-col>{{
|
|
5
|
-
$t(
|
|
6
|
-
'windward.core.components.content.blocks.generate_questions.ai_assistance'
|
|
7
|
-
)
|
|
8
|
-
}}</v-col>
|
|
9
|
-
<v-col>
|
|
10
|
-
<v-select
|
|
11
|
-
v-model="selectedContent"
|
|
12
|
-
:items="flattenedContent"
|
|
13
|
-
class="btn-selector"
|
|
14
|
-
outlined
|
|
15
|
-
hide-details
|
|
16
|
-
dense
|
|
17
|
-
:label="
|
|
18
|
-
$t(
|
|
19
|
-
'windward.core.components.content.blocks.generate_questions.selected_pages'
|
|
20
|
-
)
|
|
21
|
-
"
|
|
22
|
-
item-text="content.name"
|
|
23
|
-
return-object
|
|
24
|
-
></v-select>
|
|
25
|
-
</v-col>
|
|
26
|
-
<v-col>
|
|
27
|
-
<v-select
|
|
28
|
-
v-model="selectedDifficulty"
|
|
29
|
-
:items="taxonomyLevels"
|
|
30
|
-
item-text="text"
|
|
31
|
-
class="btn-selector"
|
|
32
|
-
outlined
|
|
33
|
-
:hide-details="!isBucketGameType"
|
|
34
|
-
dense
|
|
35
|
-
:label="
|
|
36
|
-
$t(
|
|
37
|
-
'windward.core.components.content.blocks.generate_questions.blooms.blooms_taxonomy'
|
|
38
|
-
)
|
|
39
|
-
"
|
|
40
|
-
return-object
|
|
41
|
-
></v-select>
|
|
42
|
-
<div v-if="isBucketGameType" class="text-caption mt-1">
|
|
43
|
-
{{ $t('windward.core.components.content.blocks.generate_questions.replaces_content') }}
|
|
44
|
-
</div>
|
|
45
|
-
</v-col>
|
|
46
|
-
<v-col
|
|
47
|
-
v-if="isFlashcardType || isMatchingGameType || isSortingGameType || isWordJumbleType"
|
|
48
|
-
cols="auto"
|
|
49
|
-
class="d-flex align-center"
|
|
50
|
-
>
|
|
51
|
-
<v-switch
|
|
52
|
-
v-model="replaceExisting"
|
|
53
|
-
hide-details
|
|
54
|
-
dense
|
|
55
|
-
color="secondary"
|
|
56
|
-
class="mt-0 pt-0"
|
|
57
|
-
:label="replaceExistingLabel"
|
|
58
|
-
:disabled="isLoading"
|
|
59
|
-
></v-switch>
|
|
60
|
-
</v-col>
|
|
61
|
-
<v-col>
|
|
62
|
-
<v-btn
|
|
63
|
-
elevation="0"
|
|
64
|
-
color="secondary"
|
|
65
|
-
class="mb-1 btn-selector"
|
|
66
|
-
:loading="isLoading"
|
|
67
|
-
:disabled="isLoading"
|
|
68
|
-
@click="generateAIQuestion"
|
|
69
|
-
>
|
|
70
|
-
<v-icon v-if="!isLoading" class="pr-1"
|
|
71
|
-
>mdi-magic-staff</v-icon
|
|
72
|
-
>
|
|
73
|
-
{{ buttonLabel }}
|
|
74
|
-
<template v-slot:loader>
|
|
75
|
-
<v-progress-circular
|
|
76
|
-
indeterminate
|
|
77
|
-
size="23"
|
|
78
|
-
></v-progress-circular>
|
|
79
|
-
</template>
|
|
80
|
-
</v-btn>
|
|
81
|
-
</v-col>
|
|
82
|
-
</v-row>
|
|
83
|
-
</div>
|
|
84
|
-
</template>
|
|
85
|
-
|
|
86
|
-
<script>
|
|
87
|
-
import _ from 'lodash'
|
|
88
|
-
import { mapGetters } from 'vuex'
|
|
89
|
-
import AssessmentQuestion from '~/models/AssessmentQuestion'
|
|
90
|
-
import Course from '~/models/Course'
|
|
91
|
-
import Assessment from '~/models/Assessment'
|
|
92
|
-
import Content from '~/models/Content'
|
|
93
|
-
import Activity from '../../models/Activity'
|
|
94
|
-
|
|
95
|
-
export default {
|
|
96
|
-
name: 'GenerateAIQuestionButton',
|
|
97
|
-
props: {
|
|
98
|
-
course: { type: Object, required: true },
|
|
99
|
-
content: { type: Object, required: true },
|
|
100
|
-
block: { type: Object, required: true },
|
|
101
|
-
questionType: { type: String, required: true },
|
|
102
|
-
replaceExistingMode: { type: Boolean, default: false },
|
|
103
|
-
},
|
|
104
|
-
data() {
|
|
105
|
-
return {
|
|
106
|
-
isLoading: false,
|
|
107
|
-
selectedContent: '',
|
|
108
|
-
selectedDifficulty: {
|
|
109
|
-
value: 'None',
|
|
110
|
-
text: this.$t(
|
|
111
|
-
'windward.core.components.content.blocks.generate_questions.blooms.none'
|
|
112
|
-
),
|
|
113
|
-
},
|
|
114
|
-
replaceExisting: this.questionType === 'bucket_game' || this.questionType === 'word_jumble' ? true : this.replaceExistingMode,
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
computed: {
|
|
118
|
-
...mapGetters({
|
|
119
|
-
contentTree: 'content/getTree',
|
|
120
|
-
}),
|
|
121
|
-
isFlashcardType() {
|
|
122
|
-
return this.questionType === 'flashcard'
|
|
123
|
-
},
|
|
124
|
-
isBucketGameType() {
|
|
125
|
-
return this.questionType === 'bucket_game'
|
|
126
|
-
},
|
|
127
|
-
isMatchingGameType() {
|
|
128
|
-
|
|
129
|
-
return this.questionType === 'matching_game'
|
|
130
|
-
},
|
|
131
|
-
isSortingGameType() {
|
|
132
|
-
return this.questionType === 'sorting_game'
|
|
133
|
-
},
|
|
134
|
-
isScenarioGameType() {
|
|
135
|
-
return this.questionType === 'scenario_game'
|
|
136
|
-
},
|
|
137
|
-
isWordJumbleType() {
|
|
138
|
-
return this.questionType === 'word_jumble'
|
|
139
|
-
},
|
|
140
|
-
replaceExistingLabel() {
|
|
141
|
-
if (this.isBucketGameType) {
|
|
142
|
-
return this.$t(
|
|
143
|
-
'windward.games.components.settings.bucket_game.form.replace_existing'
|
|
144
|
-
)
|
|
145
|
-
return this.$t(
|
|
146
|
-
'windward.games.components.settings.bucket_game.form.replace_existing'
|
|
147
|
-
)
|
|
148
|
-
}
|
|
149
|
-
if (this.isMatchingGameType) {
|
|
150
|
-
return this.$t(
|
|
151
|
-
'windward.games.components.settings.matching_game.form.replace_existing')
|
|
152
|
-
}
|
|
153
|
-
if (this.isSortingGameType) {
|
|
154
|
-
return this.$t(
|
|
155
|
-
'windward.games.components.settings.sorting_game.form.replace_existing')
|
|
156
|
-
}
|
|
157
|
-
if (this.isWordJumbleType) {
|
|
158
|
-
return this.$t(
|
|
159
|
-
'windward.games.components.settings.word_jumble.form.replace_existing')
|
|
160
|
-
}
|
|
161
|
-
return this.$t(
|
|
162
|
-
'windward.games.components.settings.flashcard.form.replace_existing'
|
|
163
|
-
|
|
164
|
-
)
|
|
165
|
-
},
|
|
166
|
-
flattenedContent() {
|
|
167
|
-
let cloneContentTree = _.cloneDeep(this.contentTree)
|
|
168
|
-
const homepage = this.$ContentService.getHomepage()
|
|
169
|
-
if (!_.isEmpty(homepage)) {
|
|
170
|
-
cloneContentTree.unshift(homepage)
|
|
171
|
-
}
|
|
172
|
-
let fullTree = []
|
|
173
|
-
// flatten content tree to get nested children pages
|
|
174
|
-
cloneContentTree.forEach((content) => {
|
|
175
|
-
fullTree.push(content)
|
|
176
|
-
if (content.children.length > 0) {
|
|
177
|
-
fullTree = fullTree.concat(_.flatten(content.children))
|
|
178
|
-
// check if children have children
|
|
179
|
-
content.children.forEach((child) => {
|
|
180
|
-
fullTree = fullTree.concat(_.flatten(child.children))
|
|
181
|
-
})
|
|
182
|
-
}
|
|
183
|
-
})
|
|
184
|
-
//
|
|
185
|
-
if (_.isEmpty(this.selectedContent)) {
|
|
186
|
-
// returns array so hold here to pluck out below
|
|
187
|
-
const currentPage = fullTree.filter(
|
|
188
|
-
(contentPage) => contentPage.id === this.content.id
|
|
189
|
-
)
|
|
190
|
-
this.selectedContent = currentPage[0] ? currentPage[0] : ''
|
|
191
|
-
}
|
|
192
|
-
return fullTree
|
|
193
|
-
},
|
|
194
|
-
taxonomyLevels() {
|
|
195
|
-
// For word jumble games, only show None, Remember, Understand, Apply
|
|
196
|
-
if (this.isWordJumbleType) {
|
|
197
|
-
return [
|
|
198
|
-
{
|
|
199
|
-
value: 'None',
|
|
200
|
-
text: this.$t(
|
|
201
|
-
'windward.core.components.content.blocks.generate_questions.blooms.none'
|
|
202
|
-
),
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
value: 'Remember',
|
|
206
|
-
text: this.$t(
|
|
207
|
-
'windward.core.components.content.blocks.generate_questions.blooms.remember'
|
|
208
|
-
),
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
value: 'Understand',
|
|
212
|
-
text: this.$t(
|
|
213
|
-
'windward.core.components.content.blocks.generate_questions.blooms.understand'
|
|
214
|
-
),
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
value: 'Apply',
|
|
218
|
-
text: this.$t(
|
|
219
|
-
'windward.core.components.content.blocks.generate_questions.blooms.apply'
|
|
220
|
-
),
|
|
221
|
-
},
|
|
222
|
-
]
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// For scenario games, only show None, Apply, Analyze, Evaluate
|
|
226
|
-
if (this.isScenarioGameType) {
|
|
227
|
-
return [
|
|
228
|
-
{
|
|
229
|
-
value: 'None',
|
|
230
|
-
text: this.$t(
|
|
231
|
-
'windward.core.components.content.blocks.generate_questions.blooms.none'
|
|
232
|
-
),
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
value: 'Apply',
|
|
236
|
-
text: this.$t(
|
|
237
|
-
'windward.core.components.content.blocks.generate_questions.blooms.apply'
|
|
238
|
-
),
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
value: 'Analyze',
|
|
242
|
-
text: this.$t(
|
|
243
|
-
'windward.core.components.content.blocks.generate_questions.blooms.analyze'
|
|
244
|
-
),
|
|
245
|
-
},
|
|
246
|
-
{
|
|
247
|
-
value: 'Evaluate',
|
|
248
|
-
text: this.$t(
|
|
249
|
-
'windward.core.components.content.blocks.generate_questions.blooms.evaluate'
|
|
250
|
-
),
|
|
251
|
-
},
|
|
252
|
-
]
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Basic Bloom's taxonomy levels available to all question types
|
|
256
|
-
let basicBloomTaxonomy = [
|
|
257
|
-
{
|
|
258
|
-
value: 'None',
|
|
259
|
-
text: this.$t(
|
|
260
|
-
'windward.core.components.content.blocks.generate_questions.blooms.none'
|
|
261
|
-
),
|
|
262
|
-
},
|
|
263
|
-
{
|
|
264
|
-
value: 'Remember',
|
|
265
|
-
text: this.$t(
|
|
266
|
-
'windward.core.components.content.blocks.generate_questions.blooms.remember'
|
|
267
|
-
),
|
|
268
|
-
},
|
|
269
|
-
{
|
|
270
|
-
value: 'Understand',
|
|
271
|
-
text: this.$t(
|
|
272
|
-
'windward.core.components.content.blocks.generate_questions.blooms.understand'
|
|
273
|
-
),
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
value: 'Apply',
|
|
277
|
-
text: this.$t(
|
|
278
|
-
'windward.core.components.content.blocks.generate_questions.blooms.apply'
|
|
279
|
-
),
|
|
280
|
-
},
|
|
281
|
-
]
|
|
282
|
-
|
|
283
|
-
// Only add higher-level Bloom's taxonomy for supported question types
|
|
284
|
-
const supportsAdvancedTaxonomy =
|
|
285
|
-
this.isSortingGameType ||
|
|
286
|
-
this.isScenarioGameType ||
|
|
287
|
-
([
|
|
288
|
-
'multi_choice_single_answer',
|
|
289
|
-
'multi_choice_multi_answer',
|
|
290
|
-
'ordering'
|
|
291
|
-
].includes(this.questionType) &&
|
|
292
|
-
!this.isFlashcardType &&
|
|
293
|
-
!this.isBucketGameType &&
|
|
294
|
-
!this.isMatchingGameType)
|
|
295
|
-
|
|
296
|
-
if (supportsAdvancedTaxonomy) {
|
|
297
|
-
const multiBlooms = [
|
|
298
|
-
{
|
|
299
|
-
value: 'Analyze',
|
|
300
|
-
text: this.$t(
|
|
301
|
-
'windward.core.components.content.blocks.generate_questions.blooms.analyze'
|
|
302
|
-
),
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
value: 'Evaluate',
|
|
306
|
-
text: this.$t(
|
|
307
|
-
'windward.core.components.content.blocks.generate_questions.blooms.evaluate'
|
|
308
|
-
),
|
|
309
|
-
},
|
|
310
|
-
]
|
|
311
|
-
basicBloomTaxonomy = basicBloomTaxonomy.concat(multiBlooms)
|
|
312
|
-
}
|
|
313
|
-
return basicBloomTaxonomy
|
|
314
|
-
},
|
|
315
|
-
buttonLabel() {
|
|
316
|
-
if (this.questionType === 'flashcard') {
|
|
317
|
-
return this.$t(
|
|
318
|
-
'windward.core.components.content.blocks.generate_questions.button_label_flashcard'
|
|
319
|
-
)
|
|
320
|
-
} else if (this.questionType === 'bucket_game') {
|
|
321
|
-
return this.$t(
|
|
322
|
-
'windward.core.components.content.blocks.generate_questions.button_label_bucket_game'
|
|
323
|
-
)
|
|
324
|
-
} else if (this.questionType === 'matching_game') {
|
|
325
|
-
return this.$t(
|
|
326
|
-
'windward.core.components.content.blocks.generate_questions.button_label_matching_game'
|
|
327
|
-
)
|
|
328
|
-
} else if (this.questionType === 'sorting_game') {
|
|
329
|
-
return this.$t(
|
|
330
|
-
'windward.core.components.content.blocks.generate_questions.button_label_sorting_game'
|
|
331
|
-
)
|
|
332
|
-
} else if (this.questionType === 'scenario_game') {
|
|
333
|
-
return this.$t(
|
|
334
|
-
'windward.core.components.content.blocks.generate_questions.button_label_scenario_game'
|
|
335
|
-
)
|
|
336
|
-
} else if (this.questionType === 'word_jumble') {
|
|
337
|
-
return this.$t(
|
|
338
|
-
'windward.core.components.content.blocks.generate_questions.button_label_word_jumble'
|
|
339
|
-
)
|
|
340
|
-
} else {
|
|
341
|
-
return this.$t(
|
|
342
|
-
'windward.core.components.content.blocks.generate_questions.button_label'
|
|
343
|
-
)
|
|
344
|
-
}
|
|
345
|
-
},
|
|
346
|
-
},
|
|
347
|
-
methods: {
|
|
348
|
-
async generateAIQuestion() {
|
|
349
|
-
this.isLoading = true
|
|
350
|
-
try {
|
|
351
|
-
let bloomsRequest = ''
|
|
352
|
-
if (
|
|
353
|
-
this.selectedDifficulty.text !==
|
|
354
|
-
this.$t(
|
|
355
|
-
'windward.core.components.content.blocks.generate_questions.blooms.none'
|
|
356
|
-
)
|
|
357
|
-
) {
|
|
358
|
-
bloomsRequest = `?blooms_level=${this.selectedDifficulty.value}`
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const course = new Course(this.course)
|
|
362
|
-
const contentData = this.selectedContent || this.content
|
|
363
|
-
const content = new Content(contentData)
|
|
364
|
-
|
|
365
|
-
let response
|
|
366
|
-
if (this.questionType === 'flashcard') {
|
|
367
|
-
// FLASHCARD GENERATION
|
|
368
|
-
const activity = new Activity()
|
|
369
|
-
|
|
370
|
-
const endpoint = `suggest/flashcard${bloomsRequest}`
|
|
371
|
-
|
|
372
|
-
// Call the endpoint exactly like FlashCardSlidesManager does
|
|
373
|
-
response = await Activity.custom(
|
|
374
|
-
course,
|
|
375
|
-
content,
|
|
376
|
-
activity,
|
|
377
|
-
endpoint
|
|
378
|
-
).get()
|
|
379
|
-
|
|
380
|
-
let activityData = null
|
|
381
|
-
|
|
382
|
-
if (response && response.activity) {
|
|
383
|
-
activityData = response.activity
|
|
384
|
-
} else if (
|
|
385
|
-
response &&
|
|
386
|
-
response.length > 0 &&
|
|
387
|
-
response[0] &&
|
|
388
|
-
response[0].activity
|
|
389
|
-
) {
|
|
390
|
-
activityData = response[0].activity
|
|
391
|
-
} else if (Array.isArray(response) && response.length > 0) {
|
|
392
|
-
activityData = response[0]
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (
|
|
396
|
-
activityData &&
|
|
397
|
-
activityData.metadata &&
|
|
398
|
-
activityData.metadata.config &&
|
|
399
|
-
activityData.metadata.config.cards &&
|
|
400
|
-
Array.isArray(activityData.metadata.config.cards)
|
|
401
|
-
) {
|
|
402
|
-
// We pass the activity data and the replace flag to the parent component
|
|
403
|
-
this.$emit(
|
|
404
|
-
'click:generate',
|
|
405
|
-
activityData,
|
|
406
|
-
this.replaceExisting
|
|
407
|
-
)
|
|
408
|
-
} else {
|
|
409
|
-
throw new Error(
|
|
410
|
-
'Invalid response from flashcard generation'
|
|
411
|
-
)
|
|
412
|
-
}
|
|
413
|
-
} else if (this.questionType === 'bucket_game') {
|
|
414
|
-
// BUCKET GAME GENERATION
|
|
415
|
-
const activity = new Activity()
|
|
416
|
-
|
|
417
|
-
const endpoint = `suggest/bucket_game${bloomsRequest}`
|
|
418
|
-
|
|
419
|
-
response = await Activity.custom(
|
|
420
|
-
course,
|
|
421
|
-
content,
|
|
422
|
-
activity,
|
|
423
|
-
endpoint
|
|
424
|
-
).get()
|
|
425
|
-
|
|
426
|
-
let activityData = null
|
|
427
|
-
|
|
428
|
-
if (response && response.activity) {
|
|
429
|
-
activityData = response.activity
|
|
430
|
-
} else if (
|
|
431
|
-
response &&
|
|
432
|
-
response.length > 0 &&
|
|
433
|
-
response[0] &&
|
|
434
|
-
response[0].activity
|
|
435
|
-
) {
|
|
436
|
-
activityData = response[0].activity
|
|
437
|
-
} else if (Array.isArray(response) && response.length > 0) {
|
|
438
|
-
activityData = response[0]
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if (
|
|
442
|
-
activityData &&
|
|
443
|
-
activityData.metadata &&
|
|
444
|
-
activityData.metadata.config &&
|
|
445
|
-
activityData.metadata.config.bucket_titles &&
|
|
446
|
-
activityData.metadata.config.bucket_answers
|
|
447
|
-
) {
|
|
448
|
-
// For bucket games, always use replace mode
|
|
449
|
-
this.$emit(
|
|
450
|
-
'click:generate',
|
|
451
|
-
activityData,
|
|
452
|
-
true
|
|
453
|
-
)
|
|
454
|
-
} else {
|
|
455
|
-
throw new Error(
|
|
456
|
-
'Invalid response from bucket game generation'
|
|
457
|
-
)
|
|
458
|
-
}
|
|
459
|
-
} else if (this.questionType === 'matching_game') {
|
|
460
|
-
// MATCHING GAME GENERATION
|
|
461
|
-
const activity = new Activity()
|
|
462
|
-
|
|
463
|
-
const endpoint = `suggest/matching_game${bloomsRequest}`;
|
|
464
|
-
|
|
465
|
-
response = await Activity.custom(
|
|
466
|
-
course,
|
|
467
|
-
content,
|
|
468
|
-
activity,
|
|
469
|
-
endpoint
|
|
470
|
-
).get()
|
|
471
|
-
|
|
472
|
-
let activityData = null
|
|
473
|
-
|
|
474
|
-
if (response && response.activity) {
|
|
475
|
-
activityData = response.activity
|
|
476
|
-
} else if (
|
|
477
|
-
response &&
|
|
478
|
-
response.length > 0 &&
|
|
479
|
-
response[0] &&
|
|
480
|
-
response[0].activity
|
|
481
|
-
) {
|
|
482
|
-
activityData = response[0].activity
|
|
483
|
-
} else if (Array.isArray(response) && response.length > 0) {
|
|
484
|
-
activityData = response[0]
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
if (activityData && activityData.metadata &&
|
|
488
|
-
activityData.metadata.config &&
|
|
489
|
-
activityData.metadata.config.answerObjects &&
|
|
490
|
-
activityData.metadata.config.prompts
|
|
491
|
-
) {
|
|
492
|
-
// We pass the activity data and the replace flag to the parent component
|
|
493
|
-
this.$emit(
|
|
494
|
-
'click:generate',
|
|
495
|
-
activityData,
|
|
496
|
-
this.replaceExisting
|
|
497
|
-
)
|
|
498
|
-
} else {
|
|
499
|
-
throw new Error(
|
|
500
|
-
'Invalid response from matching game generation'
|
|
501
|
-
)
|
|
502
|
-
}
|
|
503
|
-
} else if (this.questionType === 'sorting_game') {
|
|
504
|
-
// SORTING GAME GENERATION
|
|
505
|
-
const activity = new Activity()
|
|
506
|
-
|
|
507
|
-
const endpoint = `suggest/sorting_game${bloomsRequest}`;
|
|
508
|
-
|
|
509
|
-
response = await Activity.custom(
|
|
510
|
-
course,
|
|
511
|
-
content,
|
|
512
|
-
activity,
|
|
513
|
-
endpoint
|
|
514
|
-
).get()
|
|
515
|
-
|
|
516
|
-
let activityData = null
|
|
517
|
-
|
|
518
|
-
if (response && response.activity) {
|
|
519
|
-
activityData = response.activity
|
|
520
|
-
} else if (
|
|
521
|
-
response &&
|
|
522
|
-
response.length > 0 &&
|
|
523
|
-
response[0] &&
|
|
524
|
-
response[0].activity
|
|
525
|
-
) {
|
|
526
|
-
activityData = response[0].activity
|
|
527
|
-
} else if (Array.isArray(response) && response.length > 0) {
|
|
528
|
-
activityData = response[0]
|
|
529
|
-
} else if (response) {
|
|
530
|
-
activityData = response
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
if (activityData && activityData.metadata &&
|
|
534
|
-
activityData.metadata.config &&
|
|
535
|
-
activityData.metadata.config.answer &&
|
|
536
|
-
Array.isArray(activityData.metadata.config.answer)
|
|
537
|
-
) {
|
|
538
|
-
// We pass the activity data and the replace flag to the parent component
|
|
539
|
-
this.$emit(
|
|
540
|
-
'click:generate',
|
|
541
|
-
activityData,
|
|
542
|
-
this.replaceExisting
|
|
543
|
-
)
|
|
544
|
-
} else {
|
|
545
|
-
throw new Error(
|
|
546
|
-
'Invalid response from sorting game generation'
|
|
547
|
-
)
|
|
548
|
-
}
|
|
549
|
-
} else if (this.questionType === 'scenario_game') {
|
|
550
|
-
// SCENARIO GAME GENERATION
|
|
551
|
-
const activity = new Activity()
|
|
552
|
-
|
|
553
|
-
const endpoint = `suggest/scenario_game${bloomsRequest}`;
|
|
554
|
-
|
|
555
|
-
response = await Activity.custom(
|
|
556
|
-
course,
|
|
557
|
-
content,
|
|
558
|
-
activity,
|
|
559
|
-
endpoint
|
|
560
|
-
).get()
|
|
561
|
-
|
|
562
|
-
let activityData = null
|
|
563
|
-
|
|
564
|
-
if (response && response.activity) {
|
|
565
|
-
activityData = response.activity
|
|
566
|
-
} else if (
|
|
567
|
-
response &&
|
|
568
|
-
response.length > 0 &&
|
|
569
|
-
response[0] &&
|
|
570
|
-
response[0].activity
|
|
571
|
-
) {
|
|
572
|
-
activityData = response[0].activity
|
|
573
|
-
} else if (Array.isArray(response) && response.length > 0) {
|
|
574
|
-
activityData = response[0]
|
|
575
|
-
} else if (response) {
|
|
576
|
-
activityData = response
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (activityData && activityData.metadata &&
|
|
580
|
-
activityData.metadata.config &&
|
|
581
|
-
activityData.metadata.config.items &&
|
|
582
|
-
Array.isArray(activityData.metadata.config.items)
|
|
583
|
-
) {
|
|
584
|
-
// For scenario games, always replace existing content
|
|
585
|
-
this.$emit(
|
|
586
|
-
'click:generate',
|
|
587
|
-
activityData,
|
|
588
|
-
true
|
|
589
|
-
)
|
|
590
|
-
} else {
|
|
591
|
-
throw new Error('activity.error.content_mismatch')
|
|
592
|
-
}
|
|
593
|
-
} else if (this.questionType === 'word_jumble') {
|
|
594
|
-
// WORD JUMBLE GENERATION
|
|
595
|
-
const activity = new Activity()
|
|
596
|
-
|
|
597
|
-
const endpoint = `suggest/word_jumble${bloomsRequest}`
|
|
598
|
-
|
|
599
|
-
response = await Activity.custom(
|
|
600
|
-
course,
|
|
601
|
-
content,
|
|
602
|
-
activity,
|
|
603
|
-
endpoint
|
|
604
|
-
).get()
|
|
605
|
-
|
|
606
|
-
let activityData = null
|
|
607
|
-
|
|
608
|
-
if (response && response.activity) {
|
|
609
|
-
activityData = response.activity
|
|
610
|
-
} else if (
|
|
611
|
-
response &&
|
|
612
|
-
response.length > 0 &&
|
|
613
|
-
response[0] &&
|
|
614
|
-
response[0].activity
|
|
615
|
-
) {
|
|
616
|
-
activityData = response[0].activity
|
|
617
|
-
} else if (Array.isArray(response) && response.length > 0) {
|
|
618
|
-
activityData = response[0]
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
if (
|
|
622
|
-
activityData &&
|
|
623
|
-
activityData.metadata &&
|
|
624
|
-
activityData.metadata.config &&
|
|
625
|
-
activityData.metadata.config.words &&
|
|
626
|
-
Array.isArray(activityData.metadata.config.words)
|
|
627
|
-
) {
|
|
628
|
-
// We pass the activity data and the replace flag to the parent component
|
|
629
|
-
this.$emit(
|
|
630
|
-
'click:generate',
|
|
631
|
-
activityData,
|
|
632
|
-
this.replaceExisting
|
|
633
|
-
)
|
|
634
|
-
} else {
|
|
635
|
-
throw new Error(
|
|
636
|
-
'Invalid response from word jumble generation'
|
|
637
|
-
)
|
|
638
|
-
}
|
|
639
|
-
} else {
|
|
640
|
-
// ASSESSMENT QUESTION GENERATION
|
|
641
|
-
const assessment = new Assessment({ id: this.block.id })
|
|
642
|
-
const question = new AssessmentQuestion()
|
|
643
|
-
|
|
644
|
-
response = await AssessmentQuestion.custom(
|
|
645
|
-
course,
|
|
646
|
-
content,
|
|
647
|
-
assessment,
|
|
648
|
-
question,
|
|
649
|
-
`suggest/${this.questionType}${bloomsRequest}`
|
|
650
|
-
).get()
|
|
651
|
-
|
|
652
|
-
if (response && response.length > 0) {
|
|
653
|
-
const generatedQuestion = response[0]
|
|
654
|
-
this.$emit('click:generate', generatedQuestion)
|
|
655
|
-
} else {
|
|
656
|
-
throw new Error(
|
|
657
|
-
'Invalid response from question generation'
|
|
658
|
-
)
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
} catch (error) {
|
|
662
|
-
const errorMessage =
|
|
663
|
-
error.response?.data?.error?.message ||
|
|
664
|
-
error.message ||
|
|
665
|
-
'assessment.error.technical'
|
|
666
|
-
const errorType = errorMessage.split('.').pop()
|
|
667
|
-
const basePath =
|
|
668
|
-
'windward.core.components.content.blocks.generate_questions.error'
|
|
669
|
-
|
|
670
|
-
let errorText = ''
|
|
671
|
-
|
|
672
|
-
// Check for character limit error by structured data first
|
|
673
|
-
const errorSubtype = error.response?.data?.error?.details?.error_subtype
|
|
674
|
-
const errorDetails = error.response?.data?.error?.details
|
|
675
|
-
|
|
676
|
-
if (errorSubtype === 'CHARACTER_LIMIT_EXCEEDED') {
|
|
677
|
-
// Use structured data if available
|
|
678
|
-
const field = errorDetails?.field || 'content'
|
|
679
|
-
const limit = errorDetails?.limit
|
|
680
|
-
const actual = errorDetails?.actual
|
|
681
|
-
|
|
682
|
-
// Use parameterized message if we have the data
|
|
683
|
-
if (limit && actual) {
|
|
684
|
-
errorText = this.$t(`${basePath}.character_limit_detailed`, { field, limit, actual })
|
|
685
|
-
} else {
|
|
686
|
-
// Fallback to generic message
|
|
687
|
-
errorText = this.$t(`${basePath}.character_limit`)
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
// Keep backward compatibility - check for old message pattern
|
|
691
|
-
else if (errorDetails?.message === 'An error occurred while generating content. Try generating again.') {
|
|
692
|
-
errorText = this.$t(`${basePath}.character_limit`)
|
|
693
|
-
} else if (
|
|
694
|
-
// Check for content mismatch error specifically for bucket games
|
|
695
|
-
(errorMessage === 'activity.error.content_mismatch' ||
|
|
696
|
-
errorType === 'content_mismatch') &&
|
|
697
|
-
this.questionType === 'bucket_game'
|
|
698
|
-
) {
|
|
699
|
-
errorText =
|
|
700
|
-
this.$t(`${basePath}.content_mismatch_bucket_game`) +
|
|
701
|
-
'\n\n' +
|
|
702
|
-
this.$t(
|
|
703
|
-
`${basePath}.content_mismatch_bucket_game_support`
|
|
704
|
-
)
|
|
705
|
-
} else if (
|
|
706
|
-
(errorMessage === 'activity.error.content_mismatch' ||
|
|
707
|
-
errorType === 'content_mismatch') &&
|
|
708
|
-
this.questionType === 'matching_game'
|
|
709
|
-
) {
|
|
710
|
-
errorText =
|
|
711
|
-
this.$t(`${basePath}.content_mismatch_matching_game`) +
|
|
712
|
-
'\n\n' +
|
|
713
|
-
this.$t(
|
|
714
|
-
`${basePath}.content_mismatch_matching_game_support`
|
|
715
|
-
)
|
|
716
|
-
} else if (
|
|
717
|
-
(errorMessage === 'activity.error.content_mismatch' ||
|
|
718
|
-
errorType === 'content_mismatch') &&
|
|
719
|
-
this.questionType === 'sorting_game'
|
|
720
|
-
) {
|
|
721
|
-
errorText =
|
|
722
|
-
this.$t(`${basePath}.content_mismatch_sorting_game`) +
|
|
723
|
-
'\n\n' +
|
|
724
|
-
this.$t(
|
|
725
|
-
`${basePath}.content_mismatch_sorting_game_support`
|
|
726
|
-
)
|
|
727
|
-
} else if (
|
|
728
|
-
(errorMessage === 'activity.error.content_mismatch' ||
|
|
729
|
-
errorType === 'content_mismatch') &&
|
|
730
|
-
this.questionType === 'scenario_game'
|
|
731
|
-
) {
|
|
732
|
-
errorText =
|
|
733
|
-
this.$t(`${basePath}.content_mismatch_scenario_game`) +
|
|
734
|
-
'\n\n' +
|
|
735
|
-
this.$t(
|
|
736
|
-
`${basePath}.content_mismatch_scenario_game_support`
|
|
737
|
-
)
|
|
738
|
-
} else if (
|
|
739
|
-
(errorMessage === 'activity.error.content_mismatch' ||
|
|
740
|
-
errorType === 'content_mismatch') &&
|
|
741
|
-
this.questionType === 'word_jumble'
|
|
742
|
-
) {
|
|
743
|
-
errorText =
|
|
744
|
-
this.$t(`${basePath}.content_mismatch_word_jumble`) +
|
|
745
|
-
'\n\n' +
|
|
746
|
-
this.$t(
|
|
747
|
-
`${basePath}.content_mismatch_word_jumble_support`
|
|
748
|
-
)
|
|
749
|
-
} else if (errorType === 'insufficient_content' && error.response?.data?.error?.details) {
|
|
750
|
-
// Handle dynamic Bloom's taxonomy insufficient content errors
|
|
751
|
-
const details = error.response.data.error.details
|
|
752
|
-
const bloomsLevel = details.blooms_level
|
|
753
|
-
const minimumRequired = details.minimum_required
|
|
754
|
-
|
|
755
|
-
if (bloomsLevel && bloomsLevel !== 'None') {
|
|
756
|
-
// Message with Bloom's level
|
|
757
|
-
errorText =
|
|
758
|
-
this.$t(`${basePath}.insufficient_content_blooms`, { bloomsLevel }) +
|
|
759
|
-
'\n\n' +
|
|
760
|
-
this.$t(`${basePath}.insufficient_content_blooms_support`, { minimumRequired })
|
|
761
|
-
} else if (minimumRequired) {
|
|
762
|
-
// Message without Bloom's level but with word count
|
|
763
|
-
errorText =
|
|
764
|
-
this.$t(`${basePath}.insufficient_content_dynamic`) +
|
|
765
|
-
'\n\n' +
|
|
766
|
-
this.$t(`${basePath}.insufficient_content_dynamic_support`, { minimumRequired })
|
|
767
|
-
} else {
|
|
768
|
-
// Fallback to static translation
|
|
769
|
-
errorText =
|
|
770
|
-
this.$t(`${basePath}.${errorType}`) +
|
|
771
|
-
'\n\n' +
|
|
772
|
-
this.$t(`${basePath}.${errorType}_support`)
|
|
773
|
-
}
|
|
774
|
-
} else {
|
|
775
|
-
// Check if the error type is a valid i18n key
|
|
776
|
-
const errorKey = `${basePath}.${errorType}`
|
|
777
|
-
const supportKey = `${basePath}.${errorType}_support`
|
|
778
|
-
|
|
779
|
-
// Try to get the translation, fall back to default error if not found
|
|
780
|
-
const hasTranslation = this.$te(errorKey)
|
|
781
|
-
|
|
782
|
-
if (hasTranslation) {
|
|
783
|
-
errorText =
|
|
784
|
-
this.$t(errorKey) +
|
|
785
|
-
'\n\n' +
|
|
786
|
-
this.$t(supportKey)
|
|
787
|
-
} else {
|
|
788
|
-
// Fall back to default error message
|
|
789
|
-
errorText =
|
|
790
|
-
this.$t(`${basePath}.default`) +
|
|
791
|
-
'\n\n' +
|
|
792
|
-
this.$t(`${basePath}.default_support`)
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
if (errorType === 'technical') {
|
|
796
|
-
const errorCode =
|
|
797
|
-
error.response?.data?.error?.details?.error_type ||
|
|
798
|
-
'UNKNOWN'
|
|
799
|
-
errorText = errorText.replace('[ERROR_CODE]', errorCode)
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
this.$dialog.error(errorText, {
|
|
804
|
-
duration: 5000,
|
|
805
|
-
keepOnHover: true,
|
|
806
|
-
singleton: true,
|
|
807
|
-
type: 'error',
|
|
808
|
-
})
|
|
809
|
-
} finally {
|
|
810
|
-
this.isLoading = false
|
|
811
|
-
}
|
|
812
|
-
},
|
|
813
|
-
},
|
|
814
|
-
}
|
|
815
|
-
</script>
|
|
816
|
-
|
|
817
|
-
<style scoped>
|
|
818
|
-
.btn-selector {
|
|
819
|
-
width: 100%;
|
|
820
|
-
}
|
|
821
|
-
.container-generate-ai {
|
|
822
|
-
outline: 1px solid var(--v-secondary-base);
|
|
823
|
-
border-radius: 15px;
|
|
824
|
-
}
|
|
825
|
-
</style>
|
|
826
|
-
|