@windward/core 0.27.0 → 0.28.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.
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div>
2
+ <div :class="blockDirectionClasses" :dir="blockTextDirection">
3
3
  <h2
4
4
  v-if="
5
5
  block.metadata.config.title &&
@@ -13,7 +13,7 @@
13
13
  {{ block.metadata.config.instructions }}
14
14
  </p>
15
15
 
16
- <div v-if="hasSource" class="video-wrapper">
16
+ <div v-if="hasSource" class="video-wrapper" dir="ltr">
17
17
  <VuetifyPlayer
18
18
  class="no-border-player"
19
19
  :language="$i18n && $i18n.locale ? $i18n.locale : 'en-US'"
@@ -146,6 +146,124 @@ export default {
146
146
  }
147
147
  },
148
148
  computed: {
149
+ courseSourceLocale() {
150
+ // Get the source language from translation metadata
151
+ // This is the original language the course was translated FROM
152
+ // For translated courses: metadata.translation.source_language (e.g., "EN")
153
+ // For non-translated courses: falls back to locale code
154
+
155
+ // First check translation metadata for source_language (e.g., "EN", "ES")
156
+ const translationSourceLang = _.get(this.block, 'course.metadata.translation.source_language') ||
157
+ this.$store?.getters?.['course/get']?.metadata?.translation?.source_language
158
+ if (translationSourceLang) {
159
+ return translationSourceLang.toLowerCase()
160
+ }
161
+
162
+ // Try source_locale_id lookup
163
+ const translationSourceLocale = _.get(this.block, 'course.metadata.translation.source_locale_id')
164
+ if (translationSourceLocale) {
165
+ const sourceLocale = this.$store?.getters?.['locales/getById']?.(translationSourceLocale)
166
+ if (sourceLocale?.code) {
167
+ return sourceLocale.code.toLowerCase()
168
+ }
169
+ }
170
+
171
+ return (
172
+ _.get(this.block, 'course.locale.code')?.toLowerCase() ||
173
+ this.$store?.getters?.['course/sourceLocale']?.code?.toLowerCase() ||
174
+ 'en-us'
175
+ )
176
+ },
177
+ courseTargetLocale() {
178
+ // Get the target language from translation metadata
179
+ // This is the language the course was translated INTO
180
+ // For translated courses: metadata.translation.target_language (e.g., "PT-BR", "ES")
181
+
182
+ const translationTargetLang = _.get(this.block, 'course.metadata.translation.target_language') ||
183
+ this.$store?.getters?.['course/get']?.metadata?.translation?.target_language
184
+ if (translationTargetLang) {
185
+ return translationTargetLang.toLowerCase()
186
+ }
187
+
188
+ // Try target_locale_id lookup
189
+ const translationTargetLocale = _.get(this.block, 'course.metadata.translation.target_locale_id')
190
+ if (translationTargetLocale) {
191
+ const targetLocale = this.$store?.getters?.['locales/getById']?.(translationTargetLocale)
192
+ if (targetLocale?.code) {
193
+ return targetLocale.code.toLowerCase()
194
+ }
195
+ }
196
+
197
+ // No target language - course is not translated
198
+ return null
199
+ },
200
+ courseCurrentLocale() {
201
+ // Get the current course's locale code (the language this course is in)
202
+ // For a translated course, this is the target language
203
+ // Priority: translation target_language > block.course.locale > store getters > fallback
204
+
205
+ // For translated courses, use target_language directly
206
+ if (this.courseTargetLocale) {
207
+ return this.courseTargetLocale
208
+ }
209
+
210
+ const localeCode =
211
+ _.get(this.block, 'course.locale.code')?.toLowerCase() ||
212
+ _.get(this.block, 'course.locale.name')?.toLowerCase() ||
213
+ this.$store?.getters?.['course/localeCode']?.toLowerCase() ||
214
+ this.$store?.getters?.['course/locale']?.code?.toLowerCase() ||
215
+ this.$store?.getters?.['course/locale']?.name?.toLowerCase() ||
216
+ this.$store?.state?.course?.course?.locale?.code?.toLowerCase() ||
217
+ this.$store?.state?.course?.course?.locale?.name?.toLowerCase()
218
+
219
+ return localeCode || this.courseSourceLocale
220
+ },
221
+ isTranslatedCourse() {
222
+ // Check if this course is a translated course (has translation metadata)
223
+ return !!(
224
+ _.get(this.block, 'course.metadata.translation') ||
225
+ this.$store?.getters?.['course/get']?.metadata?.translation
226
+ )
227
+ },
228
+ courseTargetLocaleId() {
229
+ // Get the target_locale_id from translation metadata (UUID)
230
+ // This is used to match VTT files which are linked by locale_id
231
+ return _.get(this.block, 'course.metadata.translation.target_locale_id') ||
232
+ this.$store?.getters?.['course/get']?.metadata?.translation?.target_locale_id ||
233
+ _.get(this.block, 'course.locale_id') ||
234
+ this.$store?.getters?.['course/get']?.locale_id ||
235
+ null
236
+ },
237
+ allowedCaptionLocales() {
238
+ // Only show captions for the source language and the current course's language
239
+ // This prevents showing all translated captions (FR, ES, DE, etc.) when viewing a specific course
240
+ const locales = new Set()
241
+
242
+ // Helper to add both full locale and base language code
243
+ const addLocale = (locale) => {
244
+ if (!locale) return
245
+ locales.add(locale)
246
+ locales.add(locale.toLowerCase())
247
+ // Also add base language code (e.g., "en" from "en-US", "fr" from "fr-FR")
248
+ const base = locale.split('-')[0]?.toLowerCase()
249
+ if (base) {
250
+ locales.add(base)
251
+ }
252
+ }
253
+
254
+ // Always include the source language (original English captions)
255
+ addLocale(this.courseSourceLocale)
256
+
257
+ // Include the current course's language
258
+ addLocale(this.courseCurrentLocale)
259
+
260
+ // Also include tracks with no locale (legacy/default tracks)
261
+ locales.add(null)
262
+ locales.add(undefined)
263
+ locales.add('')
264
+
265
+ return locales
266
+ },
149
267
  hasSource() {
150
268
  // __isDirty is used to communicate from the settings panel that a source / caption
151
269
  // has changed and to reload the video player
@@ -175,12 +293,38 @@ export default {
175
293
  let file = this.resolveAsset(
176
294
  playlist[index].sources[sourceIndex]
177
295
  )
178
- if (
179
- !_.isEmpty(_.get(file, 'asset.metadata.notes')) &&
180
- //check the file notes are actually text and not an empty div
181
- this.hasWords(_.get(file, 'asset.metadata.notes'))
182
- ) {
183
- result.push(_.get(file, 'asset.metadata.notes'))
296
+
297
+ // Try to get translated notes from VTT file (for translated courses)
298
+ let notes = null
299
+ if (this.isTranslatedCourse) {
300
+ const linkedCaptions = this.getAllLinkedCaptions(file)
301
+ // Find VTT matching by locale_id (UUID) or locale.code (language code)
302
+ const currentVtt = _.find(linkedCaptions, vtt => {
303
+ // First try matching by locale_id (most reliable)
304
+ if (this.courseTargetLocaleId && vtt.locale_id === this.courseTargetLocaleId) {
305
+ return true
306
+ }
307
+ // Also try matching by locale.code
308
+ const vttLocale = _.get(vtt, 'locale.code', '').toLowerCase()
309
+ return vttLocale && this.isAllowedCaptionLanguage(vttLocale) &&
310
+ vttLocale === this.courseCurrentLocale
311
+ })
312
+ // Notes can be in different locations:
313
+ // - vtt.metadata.notes (FileAssetMap metadata)
314
+ // - vtt.notes (FileAssetMapResource exposes this)
315
+ // - vtt.asset.metadata.notes (FileAsset metadata - where translation stores them)
316
+ notes = _.get(currentVtt, 'metadata.notes') ||
317
+ _.get(currentVtt, 'notes') ||
318
+ _.get(currentVtt, 'asset.metadata.notes')
319
+ }
320
+
321
+ // Fallback to video asset notes
322
+ if (!notes || !this.hasWords(notes)) {
323
+ notes = _.get(file, 'asset.metadata.notes')
324
+ }
325
+
326
+ if (notes && this.hasWords(notes)) {
327
+ result.push(notes)
184
328
  }
185
329
  }
186
330
  }
@@ -192,36 +336,123 @@ export default {
192
336
  linkedPlaylist() {
193
337
  const playlist = _.cloneDeep(this.block.metadata.config.playlist)
194
338
  for (const index in playlist) {
339
+ // Build comprehensive track list from multiple sources
340
+ const allTracks = []
341
+ const addedSrcs = new Set() // Track added URLs to avoid duplicates
342
+ const addedLangs = new Set() // Track added languages to avoid duplicate srclang keys
343
+
344
+ // 1. FIRST: Resolve video files and get linked captions BEFORE transforming sources
345
+ const resolvedVideoFiles = []
195
346
  for (const sourceIndex in playlist[index].sources) {
196
- let file = this.resolveAsset(
347
+ let videoFile = this.resolveAsset(
197
348
  playlist[index].sources[sourceIndex]
198
349
  )
350
+ resolvedVideoFiles.push(videoFile)
199
351
 
200
- playlist[index].sources[sourceIndex] = {
201
- src: _.get(file, 'asset.public_url', ''),
202
- type: _.get(file, 'asset.metadata.mime', ''),
203
- }
352
+ // Get ALL linked captions from this video file (adds to block.assets)
353
+ const linkedCaptions = this.getAllLinkedCaptions(videoFile)
354
+
355
+ for (const vtt of linkedCaptions) {
356
+ // Get the src - may be directly available or need to extract from asset
357
+ const src = _.get(vtt, 'asset.public_url', '')
358
+
359
+ // Skip if no src or already added
360
+ if (!src || addedSrcs.has(src)) {
361
+ continue
362
+ }
363
+
364
+ const srclang = _.get(vtt, 'locale.code') || this.courseSourceLocale
365
+
366
+ // Skip if this language was already added (avoid duplicate keys)
367
+ if (addedLangs.has(srclang)) {
368
+ continue
369
+ }
370
+
371
+ // FILTER: Only include tracks that match the course's language(s)
372
+ // - Original courses: show only the course's own language
373
+ // - Translated courses: show source language + target language
374
+ if (!this.isAllowedCaptionLanguage(srclang)) {
375
+ continue
376
+ }
377
+
378
+ const label = this.getLabelForLanguage(srclang) || _.get(vtt, 'locale.name') || srclang
204
379
 
205
- // If there's linked captions and there's no hard-set captions
206
- // Fallback to the linked captions
207
- const linkedCaptions = this.getLinkedCaptions(file)
208
- if (playlist[index].tracks.length === 0 && linkedCaptions) {
209
- playlist[index].tracks.push(linkedCaptions)
380
+ allTracks.push({
381
+ src: src,
382
+ kind: 'captions',
383
+ srclang: srclang,
384
+ label: label,
385
+ default: false,
386
+ })
387
+ addedSrcs.add(src)
388
+ addedLangs.add(srclang)
210
389
  }
211
390
  }
212
391
 
392
+ // 2. Process manually-added tracks from playlist[x].tracks[]
213
393
  for (const trackIndex in playlist[index].tracks) {
214
394
  let file = this.resolveAsset(
215
395
  playlist[index].tracks[trackIndex]
216
396
  )
217
397
 
218
- playlist[index].tracks[trackIndex] = {
219
- src: _.get(file, 'asset.public_url', ''),
398
+ const trackObj = playlist[index].tracks[trackIndex]
399
+ const src = _.get(file, 'asset.public_url', '')
400
+
401
+ // Skip if no src or already added
402
+ if (!src || addedSrcs.has(src)) {
403
+ continue
404
+ }
405
+
406
+ const srclang = trackObj.srclang || this.courseSourceLocale
407
+
408
+ // Skip if this language was already added (avoid duplicate keys)
409
+ if (addedLangs.has(srclang)) {
410
+ continue
411
+ }
412
+
413
+ // FILTER: Only include tracks that match source or target language
414
+ // For translated courses, only show source language and target language tracks
415
+ // For non-translated courses, this filter is skipped and all tracks are shown
416
+ if (this.isTranslatedCourse && !this.isAllowedCaptionLanguage(srclang)) {
417
+ continue
418
+ }
419
+
420
+ const label = trackObj.label || this.getLabelForLanguage(srclang)
421
+
422
+ allTracks.push({
423
+ src: src,
220
424
  kind: 'captions',
221
- srclang: 'en-US',
222
- default: true,
425
+ srclang: srclang,
426
+ label: label,
427
+ default: false,
428
+ })
429
+ addedSrcs.add(src)
430
+ addedLangs.add(srclang)
431
+ }
432
+
433
+ // 3. NOW transform video sources to player format
434
+ for (const sourceIndex in playlist[index].sources) {
435
+ const file = resolvedVideoFiles[sourceIndex]
436
+ playlist[index].sources[sourceIndex] = {
437
+ src: _.get(file, 'asset.public_url', ''),
438
+ type: _.get(file, 'asset.metadata.mime', ''),
223
439
  }
224
440
  }
441
+
442
+ // 4. Sort tracks: course source language first
443
+ allTracks.sort((a, b) => {
444
+ if (a.srclang === this.courseSourceLocale) return -1
445
+ if (b.srclang === this.courseSourceLocale) return 1
446
+ return 0
447
+ })
448
+
449
+ // 5. Set default on first track (should be source language)
450
+ if (allTracks.length > 0) {
451
+ allTracks[0].default = true
452
+ }
453
+
454
+ // 6. Replace tracks array with processed tracks
455
+ playlist[index].tracks = allTracks
225
456
  for (const adIndex in playlist[index].ads) {
226
457
  for (const adSourceIndex in playlist[index].ads[adIndex]
227
458
  .sources) {
@@ -354,6 +585,9 @@ export default {
354
585
  },
355
586
  },
356
587
  },
588
+ mounted() {
589
+ // Video block mounted - locale filtering applied via isTranslatedCourse check
590
+ },
357
591
  beforeMount() {
358
592
  // Apply the default config
359
593
  if (_.isEmpty(this.block.metadata.config)) {
@@ -400,6 +634,48 @@ export default {
400
634
  async onBeforeSave() {
401
635
  this.block.body = 'video'
402
636
  },
637
+ /**
638
+ * Get ALL linked VTT caption files (supports multiple languages)
639
+ * @param {Object} file - The video file object with linked_assets
640
+ * @returns {Array} Array of VTT files with locale information
641
+ */
642
+ getAllLinkedCaptions(file) {
643
+ // Get ALL VTT files from linked_assets (not just first one)
644
+ const linkedCaptions = _.filter(
645
+ _.get(file, 'asset.linked_assets', []),
646
+ (f) => _.get(f, 'asset.metadata.extension', '') === 'vtt'
647
+ )
648
+
649
+ // Add locale information to each VTT AND add to block.assets
650
+ return linkedCaptions.map(vtt => {
651
+ // CRITICAL: Add to block.assets so resolveAsset can find it
652
+ const foundAsset = this.block.assets.find((a) => {
653
+ return a.file_asset_id === vtt.file_asset_id
654
+ })
655
+ if (!foundAsset) {
656
+ this.block.assets.push(_.cloneDeep(vtt))
657
+ }
658
+
659
+ // Try to get locale - priority: API response > store lookup > null
660
+ let locale = vtt.locale || null
661
+
662
+ // If no locale from API but we have locale_id, try store lookup
663
+ if (!locale && vtt.locale_id && this.$store?.getters?.['locales/getById']) {
664
+ locale = this.$store.getters['locales/getById'](vtt.locale_id)
665
+ }
666
+
667
+ return {
668
+ ...vtt,
669
+ locale: locale
670
+ }
671
+ })
672
+ },
673
+
674
+ /**
675
+ * Get single linked caption (backward compatibility)
676
+ * @param {Object} file - The video file object
677
+ * @returns {Object|null} First VTT file found
678
+ */
403
679
  getLinkedCaptions(file) {
404
680
  // Check to see if the video source has a linked asset and it's a vtt file
405
681
  // Prefer the most recently linked VTT (last match), not the oldest.
@@ -431,6 +707,69 @@ export default {
431
707
  return linkedCaption || null
432
708
  },
433
709
 
710
+ /**
711
+ * TODO:find a better place for this to live
712
+ * Get human-readable language label for a language code
713
+ * @param {string} langCode - ISO language code (e.g., 'en-US')
714
+ * @returns {string} Language name
715
+ */
716
+ getLabelForLanguage(langCode) {
717
+ // Map common language codes to display names
718
+ const labels = {
719
+ 'en': 'English',
720
+ 'en-US': 'English',
721
+ 'en-GB': 'English (UK)',
722
+ 'es': 'Spanish',
723
+ 'es-ES': 'Spanish',
724
+ 'es-MX': 'Spanish (Mexico)',
725
+ 'fr': 'French',
726
+ 'fr-FR': 'French',
727
+ 'fr-CA': 'French (Canada)',
728
+ 'de': 'German',
729
+ 'de-DE': 'German',
730
+ 'it': 'Italian',
731
+ 'it-IT': 'Italian',
732
+ 'pt': 'Portuguese',
733
+ 'pt-BR': 'Portuguese (Brazil)',
734
+ 'pt-PT': 'Portuguese (Portugal)',
735
+ 'zh': 'Chinese',
736
+ 'zh-CN': 'Chinese (Simplified)',
737
+ 'zh-TW': 'Chinese (Traditional)',
738
+ 'ja': 'Japanese',
739
+ 'ja-JP': 'Japanese',
740
+ 'ko': 'Korean',
741
+ 'ko-KR': 'Korean',
742
+ 'ru': 'Russian',
743
+ 'ru-RU': 'Russian',
744
+ 'ar': 'Arabic',
745
+ 'ar-SA': 'Arabic',
746
+ 'hi': 'Hindi',
747
+ 'hi-IN': 'Hindi',
748
+ }
749
+ return labels[langCode] || langCode
750
+ },
751
+
752
+ /**
753
+ * Check if a caption language is allowed based on course source/target languages
754
+ * Since allowedCaptionLocales already contains both full codes (en-us) and base codes (en),
755
+ * we just need to check if the language or its base is in the Set.
756
+ * @param {string} lang - The language code to check (e.g., 'en-US', 'PT-BR')
757
+ * @returns {boolean} - True if the language is allowed
758
+ */
759
+ isAllowedCaptionLanguage(lang) {
760
+ if (!lang) {
761
+ // Allow tracks with no language set (legacy/default tracks)
762
+ return true
763
+ }
764
+
765
+ const langLower = lang.toLowerCase()
766
+ const langBase = langLower.split('-')[0]
767
+
768
+ // Check if the full language code or its base is in allowedCaptionLocales
769
+ return this.allowedCaptionLocales.has(langLower) ||
770
+ this.allowedCaptionLocales.has(langBase)
771
+ },
772
+
434
773
  /**
435
774
  * Check if the given text has words, omitting HTML tags and HTML entities
436
775
  * @param {string} text - The text to check
@@ -310,8 +310,8 @@ export default {
310
310
  }
311
311
  if (_.isEmpty(this.block.metadata.config.display)) {
312
312
  this.block.metadata.config.display = {
313
- show_title: false,
314
- show_background: false,
313
+ show_title: true,
314
+ show_background: true,
315
315
  round_icon: false,
316
316
  italic_icon: false,
317
317
  large_icon: false,
@@ -326,7 +326,7 @@ export default {
326
326
  methods: {
327
327
  onAddElement() {
328
328
  const defaultObject = {
329
- icon: '',
329
+ icon: 'mdi-star',
330
330
  fileConfig: {},
331
331
  iconImage: false,
332
332
  title: '',
@@ -1,5 +1,10 @@
1
1
  <template>
2
2
  <v-container>
3
+ <BaseContentBlockSettings
4
+ v-model="block.metadata.config"
5
+ :disabled="render"
6
+ ></BaseContentBlockSettings>
7
+ <v-divider class="my-4 primary"></v-divider>
3
8
  <ImageAssetSettings
4
9
  v-model="block.metadata.config"
5
10
  :assets.sync="block.assets"
@@ -13,11 +18,14 @@
13
18
  import _ from 'lodash'
14
19
  import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
15
20
  import ImageAssetSettings from '~/components/Content/Settings/ImageAssetSettings.vue'
21
+ import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
22
+ import Uuid from '~/helpers/Uuid'
16
23
 
17
24
  export default {
18
25
  name: 'ImageSettings',
19
26
  components: {
20
27
  ImageAssetSettings,
28
+ BaseContentBlockSettings,
21
29
  },
22
30
  extends: BaseContentSettings,
23
31
  props: {
@@ -40,6 +48,24 @@ export default {
40
48
  if (_.isEmpty(this.block.metadata.config)) {
41
49
  this.block.metadata.config = {}
42
50
  }
51
+ // If the block is brand new and the title is missing then pre-set the word 'Image' to it
52
+ if (
53
+ !Uuid.test(this.block.id) &&
54
+ _.isEmpty(this.block.metadata.config.title)
55
+ ) {
56
+ this.block.metadata.config.title = this.$t(
57
+ 'windward.core.shared.content_blocks.title.image'
58
+ )
59
+ } else if (_.isEmpty(this.block.metadata.config.title)) {
60
+ // Otherwise make sure the title key at least exists
61
+ this.block.metadata.config.title = ''
62
+ }
63
+ if (!_.isBoolean(this.block.metadata.config.display_title)) {
64
+ this.$set(this.block.metadata.config, 'display_title', true)
65
+ }
66
+ if (_.isEmpty(this.block.metadata.config.instructions)) {
67
+ this.block.metadata.config.instructions = ''
68
+ }
43
69
  if (_.isEmpty(this.block.metadata.config.asset)) {
44
70
  this.block.metadata.config.asset = null
45
71
  }
@@ -24,10 +24,25 @@
24
24
  </p>
25
25
  <TextEditor
26
26
  id="block-settings-sample-response"
27
+ ref="sampleResponseEditor"
27
28
  v-model="block.metadata.config.sample_response"
28
29
  :height="200"
30
+ :rules="sampleResponseRules"
29
31
  :disabled="render"
30
32
  ></TextEditor>
33
+ <v-row class="pt-4">
34
+ <v-col cols="12">
35
+ <v-switch
36
+ v-model="block.metadata.config.ai_mode_for_student"
37
+ :label="
38
+ $t(
39
+ 'windward.core.components.settings.open_response.ai_mode_for_student'
40
+ )
41
+ "
42
+ :disabled="render"
43
+ ></v-switch>
44
+ </v-col>
45
+ </v-row>
31
46
  <p class="pt-4">
32
47
  {{
33
48
  $t(
@@ -84,6 +99,34 @@ export default {
84
99
  course: 'course/get',
85
100
  currentContent: 'content/get',
86
101
  }),
102
+ sampleResponseRules() {
103
+ return [
104
+ (value) => {
105
+ if (!this.block?.metadata?.config?.ai_mode_for_student) {
106
+ return true
107
+ }
108
+
109
+ const text = this.stripHtmlTags(value)
110
+ return (
111
+ !_.isEmpty(text) ||
112
+ this.$t(
113
+ 'windward.core.components.settings.open_response.validation.sample_response_required_ai_mode'
114
+ )
115
+ )
116
+ },
117
+ ]
118
+ },
119
+ },
120
+ watch: {
121
+ 'block.metadata.config.ai_mode_for_student'(newValue, oldValue) {
122
+ if (newValue === oldValue) {
123
+ return
124
+ }
125
+
126
+ this.$nextTick(() => {
127
+ this.$refs.sampleResponseEditor?.validate?.()
128
+ })
129
+ },
87
130
  },
88
131
  beforeMount() {
89
132
  if (_.isEmpty(this.block)) {
@@ -117,9 +160,21 @@ export default {
117
160
  if (_.isEmpty(this.block.metadata.config.starting_text)) {
118
161
  this.block.metadata.config.starting_text = ''
119
162
  }
163
+ if (!_.isBoolean(this.block.metadata.config.ai_mode_for_student)) {
164
+ this.$set(this.block.metadata.config, 'ai_mode_for_student', false)
165
+ }
120
166
  },
121
167
  mounted() {},
122
168
  methods: {
169
+ stripHtmlTags(body) {
170
+ if (typeof body !== 'string') {
171
+ return ''
172
+ }
173
+
174
+ let text = body.replace(/&nbsp;/gi, ' ')
175
+ text = text.replace(/\u00A0/g, ' ')
176
+ return text.replace(/(<([^>]+)>)/gi, '').trim()
177
+ },
123
178
  onApplyGeneratedBlock(payload = {}) {
124
179
  if (_.isEmpty(payload)) {
125
180
  return
@@ -136,6 +191,10 @@ export default {
136
191
  _.get(config, 'sample_response', '') ?? ''
137
192
  this.block.metadata.config.starting_text =
138
193
  _.get(config, 'starting_text', '') ?? ''
194
+ if (_.has(config, 'ai_mode_for_student')) {
195
+ this.block.metadata.config.ai_mode_for_student =
196
+ config.ai_mode_for_student
197
+ }
139
198
 
140
199
  if (_.has(config, 'display_title')) {
141
200
  this.block.metadata.config.display_title = config.display_title