@windward/integrations 0.19.1 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/components/Content/Blocks/ActionPanel/TransformBlock.vue +349 -0
  3. package/components/LLM/GenerateContent/AssessmentQuestionGenerateButton.vue +180 -9
  4. package/components/LLM/GenerateContent/BlockQuestionGenerateButton.vue +35 -13
  5. package/i18n/en-US/components/content/blocks/action_panel/index.ts +5 -0
  6. package/i18n/en-US/components/content/blocks/action_panel/transform_block.ts +7 -0
  7. package/i18n/en-US/components/content/blocks/index.ts +2 -0
  8. package/i18n/en-US/components/llm/generate_content/generate_questions.ts +4 -0
  9. package/i18n/en-US/components/llm/generate_content/index.ts +1 -0
  10. package/i18n/en-US/shared/settings.ts +1 -2
  11. package/i18n/es-ES/components/content/blocks/action_panel/index.ts +5 -0
  12. package/i18n/es-ES/components/content/blocks/action_panel/transform_block.ts +7 -0
  13. package/i18n/es-ES/components/content/blocks/index.ts +2 -0
  14. package/i18n/es-ES/components/llm/generate_content/generate_questions.ts +5 -0
  15. package/i18n/es-ES/components/llm/generate_content/index.ts +1 -0
  16. package/i18n/es-ES/shared/settings.ts +1 -2
  17. package/i18n/sv-SE/components/content/blocks/action_panel/index.ts +5 -0
  18. package/i18n/sv-SE/components/content/blocks/action_panel/transform_block.ts +7 -0
  19. package/i18n/sv-SE/components/content/blocks/index.ts +2 -0
  20. package/i18n/sv-SE/components/llm/generate_content/generate_questions.ts +5 -0
  21. package/i18n/sv-SE/components/llm/generate_content/index.ts +1 -0
  22. package/i18n/sv-SE/shared/settings.ts +1 -2
  23. package/package.json +1 -1
  24. package/plugin.js +16 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## Release [0.20.0] - 2025-12-03
4
+
5
+ * Merged in feature/LE-2177-switch-block-type-for-text-heavy (pull request #118)
6
+ * Merged in feature/LE-2156-block-settings-label (pull request #115)
7
+ * Merged in bugfix/LE-1992-the-ui-of-the-ai-assistance-is-b (pull request #116)
8
+ * Merged in LE-2149/assess-inputs-rag (pull request #117)
9
+
10
+
3
11
  ## Hotfix [0.19.1] - 2025-11-07
4
12
 
5
13
  * Merged in bugfix/LE-2197-org-admin---import-from-resource (pull request #112)
@@ -0,0 +1,349 @@
1
+ <template>
2
+ <v-list-item-group v-if="canTransformBlock" :disabled="isTransforming">
3
+ <v-list-group v-model="showMenu" @click="onClickShowTransforms">
4
+ <template #activator>
5
+ <v-list-item-icon>
6
+ <v-progress-circular
7
+ v-if="isTransforming"
8
+ indeterminate
9
+ ></v-progress-circular>
10
+ <v-icon v-if="!isTransforming">mdi-shape</v-icon>
11
+ </v-list-item-icon>
12
+ <v-list-item-title>
13
+ {{
14
+ $t(
15
+ 'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type'
16
+ )
17
+ }}
18
+ </v-list-item-title>
19
+ </template>
20
+
21
+ <v-list-item
22
+ v-for="action in actions"
23
+ :key="action.tag"
24
+ :disabled="isTransforming"
25
+ @click="performBlockTransform($event, action.tag)"
26
+ >
27
+ <v-list-item-title>
28
+ <v-icon>mdi-arrow-right</v-icon> {{ action.label }}
29
+ </v-list-item-title>
30
+ </v-list-item>
31
+ </v-list-group>
32
+ </v-list-item-group>
33
+ </template>
34
+
35
+ <script>
36
+ import _ from 'lodash'
37
+ import { mapGetters } from 'vuex'
38
+ import Organization from '~/models/Organization'
39
+ import Course from '~/models/Course'
40
+
41
+ export default {
42
+ props: {
43
+ // The block associated with this action panel
44
+ value: { type: Object, required: false, default: null },
45
+
46
+ // The blocks position relative to other blocks (Potentially not the same as value.order)
47
+ index: { type: Number, required: false, default: 0 },
48
+ // The total number of blocks on the content page
49
+ length: { type: Number, required: false, default: 0 },
50
+
51
+ // The content block html element
52
+ bindElement: { type: null, required: false, default: null },
53
+
54
+ // Button enable / disable props
55
+ draggable: { type: Boolean, required: false, default: true },
56
+ savable: { type: Boolean, required: false, default: true },
57
+ editable: { type: Boolean, required: false, default: true },
58
+ deletable: { type: Boolean, required: false, default: true },
59
+ editMode: { type: Boolean, required: false, default: null },
60
+ saving: { type: Boolean, required: false, default: null },
61
+ btnGroupId: { type: String, required: false, default: '' },
62
+ },
63
+
64
+ data() {
65
+ return {
66
+ showMenu: false,
67
+ isTransforming: false,
68
+ actions: [
69
+ {
70
+ tag: 'plugin-core-accordion',
71
+ label: this.$t(
72
+ 'windward.core.shared.content_blocks.title.accordion'
73
+ ),
74
+ },
75
+ {
76
+ tag: 'plugin-core-tab',
77
+ label: this.$t(
78
+ 'windward.core.shared.content_blocks.title.tab'
79
+ ),
80
+ },
81
+ {
82
+ tag: 'plugin-core-clickable-icons',
83
+ label: this.$t(
84
+ 'windward.core.shared.content_blocks.title.clickable_icons'
85
+ ),
86
+ },
87
+ ],
88
+ }
89
+ },
90
+ computed: {
91
+ ...mapGetters({
92
+ organization: 'organization/get',
93
+ course: 'course/get',
94
+ }),
95
+ vModel: {
96
+ get() {
97
+ return this.value
98
+ },
99
+ set(value) {
100
+ this.$emit('input', value)
101
+ },
102
+ },
103
+ canTransformBlock() {
104
+ if (!this.value || this.value.tag !== 'content-blocks-text') {
105
+ return false
106
+ }
107
+
108
+ const body = _.get(this.value, 'body', '')
109
+ if (!body || typeof body !== 'string') {
110
+ return false
111
+ }
112
+
113
+ let paragraphCount = 0
114
+ try {
115
+ const doc = new DOMParser().parseFromString(body, 'text/html')
116
+ const paragraphs = Array.from(doc.querySelectorAll('p, li'))
117
+ paragraphCount = paragraphs.filter((el) =>
118
+ (el.textContent || '').trim()
119
+ ).length
120
+ } catch (_e) {
121
+ // Fallback: simple split on double line breaks
122
+ const normalized = body
123
+ .replace(/<[^>]+>/g, '\n')
124
+ .split(/\n+/)
125
+ .map((p) => p.trim())
126
+ .filter((p) => p.length > 0)
127
+ paragraphCount = normalized.length
128
+ }
129
+
130
+ return paragraphCount >= 2
131
+ },
132
+ },
133
+ methods: {
134
+ onClickShowTransforms(e) {
135
+ e.stopPropagation()
136
+ },
137
+ buildTransformedBlock(sourceBlock, data, targetType) {
138
+ const baseOrder = _.get(sourceBlock, 'order', 0)
139
+ const newBlock = {
140
+ id: _.uniqueId('create_content_block_'),
141
+ content_id: null,
142
+ tag: '',
143
+ body: '',
144
+ status: 'draft',
145
+ order: baseOrder + 0.5,
146
+ type: {
147
+ save_state: false,
148
+ trackable: false,
149
+ completable: false,
150
+ },
151
+ metadata: {
152
+ config: {},
153
+ },
154
+ }
155
+
156
+ const blockTitle = data.block_title || ''
157
+ const items = Array.isArray(data.items) ? data.items : []
158
+
159
+ if (targetType === 'plugin-core-accordion') {
160
+ newBlock.tag = 'plugin-core-accordion'
161
+ newBlock.body = this.$t(
162
+ 'windward.core.shared.content_blocks.title.accordion'
163
+ )
164
+ newBlock.metadata.config = {
165
+ title: blockTitle,
166
+ display_title: true,
167
+ instructions: this.$t(
168
+ 'windward.core.components.settings.accordion.instructions'
169
+ ),
170
+ items: items.map((item) => ({
171
+ header: item.title || '',
172
+ expand: false,
173
+ content: item.body_html || '',
174
+ fileConfig: {
175
+ display: {
176
+ width: 100,
177
+ margin: '',
178
+ padding: '',
179
+ },
180
+ hideBackground: true,
181
+ },
182
+ })),
183
+ }
184
+ } else if (targetType === 'plugin-core-tab') {
185
+ newBlock.tag = 'plugin-core-tab'
186
+ newBlock.body = this.$t(
187
+ 'windward.core.shared.content_blocks.title.tab'
188
+ )
189
+ newBlock.metadata.config = {
190
+ title: blockTitle,
191
+ display_title: true,
192
+ instructions: this.$t(
193
+ 'windward.core.components.settings.tab.instructions'
194
+ ),
195
+ currentTab: 0,
196
+ items: items.map((item) => ({
197
+ tabHeader: item.title || '',
198
+ expand: false,
199
+ content: item.body_html || '',
200
+ imageAsset: {
201
+ display: {
202
+ width: 100,
203
+ margin: '',
204
+ padding: '',
205
+ },
206
+ hideBackground: true,
207
+ },
208
+ })),
209
+ }
210
+ } else if (targetType === 'plugin-core-clickable-icons') {
211
+ newBlock.tag = 'plugin-core-clickable-icons'
212
+ newBlock.body = this.$t(
213
+ 'windward.core.shared.content_blocks.title.clickable_icons'
214
+ )
215
+ newBlock.metadata.config = {
216
+ title: blockTitle,
217
+ display_title: true,
218
+ description: this.$t(
219
+ 'windward.core.components.settings.clickable_icon.information'
220
+ ),
221
+ display: {
222
+ show_title: false,
223
+ show_background: false,
224
+ round_icon: false,
225
+ italic_icon: false,
226
+ large_icon: false,
227
+ autocolor: true,
228
+ },
229
+ items: items.map((item) => ({
230
+ icon: '',
231
+ fileConfig: {},
232
+ iconImage: false,
233
+ title: item.title || '',
234
+ body: item.body_html || '',
235
+ color: {
236
+ class: '',
237
+ },
238
+ active: false,
239
+ })),
240
+ }
241
+ }
242
+
243
+ return newBlock
244
+ },
245
+ async performBlockTransform(e, targetType) {
246
+ e.stopPropagation()
247
+
248
+ const html = _.get(this.value, 'body', '')
249
+ if (!html || typeof html !== 'string') {
250
+ return
251
+ }
252
+
253
+ if (this.isTransforming) {
254
+ return
255
+ }
256
+
257
+ const organizationId = _.get(this.organization, 'id', null)
258
+ const courseId = _.get(this.course, 'id', null)
259
+
260
+ if (!organizationId || !courseId) {
261
+ if (this.$toast) {
262
+ this.$toast.error(
263
+ this.$t(
264
+ 'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_error'
265
+ )
266
+ )
267
+ }
268
+ return
269
+ }
270
+
271
+ const request = new Course()
272
+ request.custom(
273
+ new Organization({ id: organizationId }),
274
+ new Course({ id: courseId }),
275
+ 'llm-block-transform'
276
+ )
277
+
278
+ const resourcePath = request._customResource
279
+ if (!resourcePath) {
280
+ if (this.$toast) {
281
+ this.$toast.error(
282
+ this.$t(
283
+ 'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_error'
284
+ )
285
+ )
286
+ }
287
+ return
288
+ }
289
+
290
+ const payload = {
291
+ html,
292
+ target_tag: targetType,
293
+ language: this.$i18n.locale,
294
+ }
295
+
296
+ this.isTransforming = true
297
+
298
+ try {
299
+ const requestConfig = request._reqConfig(
300
+ {
301
+ method: 'POST',
302
+ url: `${request.baseURL()}/${resourcePath}`,
303
+ data: payload,
304
+ },
305
+ { forceMethod: true }
306
+ )
307
+
308
+ const response = await request.request(requestConfig)
309
+ const data = response?.data || response
310
+
311
+ if (
312
+ !data ||
313
+ !Array.isArray(data.items) ||
314
+ data.items.length === 0
315
+ ) {
316
+ throw new Error('invalid_response')
317
+ }
318
+
319
+ const newBlock = this.buildTransformedBlock(
320
+ this.value,
321
+ data,
322
+ targetType
323
+ )
324
+
325
+ // Let the host content page own block insertion via the global event
326
+ this.$eb.$emit('create:content-block', newBlock)
327
+
328
+ // Immediately focus the new block for the user
329
+ const activeBlock = _.cloneDeep(newBlock)
330
+ this.$ContentService.setContentBlock(activeBlock)
331
+ this.$eb.$emit('block:focus', activeBlock)
332
+ } catch (e) {
333
+ // Surface errors instead of failing silently
334
+ // eslint-disable-next-line no-console
335
+ console.error('Block transform failed', e)
336
+ if (this.$toast) {
337
+ this.$toast.error(
338
+ this.$t(
339
+ 'windward.integrations.components.content.blocks.action_panel.transform_block.change_block_type_error'
340
+ )
341
+ )
342
+ }
343
+ } finally {
344
+ this.isTransforming = false
345
+ }
346
+ },
347
+ },
348
+ }
349
+ </script>
@@ -17,6 +17,13 @@
17
17
  :disabled="isLoading || disabled"
18
18
  ></BloomTaxonomySelector>
19
19
  </v-col>
20
+ <v-col v-if="questionType !== 'true_false'" cols="12">
21
+ <v-switch
22
+ v-model="keepExisting"
23
+ :disabled="isLoading || disabled"
24
+ :label="$t('windward.integrations.components.llm.generate_content.keep_existing_inputs')"
25
+ ></v-switch>
26
+ </v-col>
20
27
  <v-col cols="12">
21
28
  <v-btn
22
29
  elevation="0"
@@ -84,6 +91,7 @@ export default {
84
91
  showLoadingText: false,
85
92
  selectedContent: null,
86
93
  selectedDifficulty: null,
94
+ keepExisting: true,
87
95
  }
88
96
  },
89
97
  computed: {
@@ -116,6 +124,118 @@ export default {
116
124
  },
117
125
  },
118
126
  methods: {
127
+ buildClearedQuestion() {
128
+ const type = this.questionType
129
+ const cleared = {
130
+ question_type: type,
131
+ assessment_question_type_id: _.get(this.value, 'assessment_question_type_id', null),
132
+ body: '',
133
+ question_metadata: {},
134
+ answer_metadata: {},
135
+ metadata: {},
136
+ }
137
+
138
+ if (type === 'multi_choice_single_answer' || type === 'multi_choice_multi_answer') {
139
+ cleared.question_metadata = { options: [] }
140
+ cleared.answer_metadata = { feedback: {} }
141
+ } else if (type === 'matching_text') {
142
+ cleared.question_metadata = { prompts: [], choices: [] }
143
+ cleared.answer_metadata = { map: [], feedback: {} }
144
+ } else if (type === 'ordering') {
145
+ cleared.question_metadata = { prompts: [] }
146
+ cleared.answer_metadata = { map: [], feedback: {} }
147
+ } else if (type === 'fill_in_the_blank') {
148
+ cleared.question_metadata = _.get(this.value, 'question_metadata', {}) || {}
149
+ cleared.answer_metadata = { correct_answer_options: [], feedback: {} }
150
+ }
151
+
152
+ return cleared
153
+ },
154
+ buildExistingInputs() {
155
+ const q = _.cloneDeep(this.value) || {}
156
+ const type = this.questionType
157
+
158
+ const nonEmpty = (s) => typeof s === 'string' && s.replace(/<[^>]*>/g, '').trim().length > 0
159
+
160
+ const base = { body: q.body || '' }
161
+
162
+ if (type === 'multi_choice_single_answer' || type === 'multi_choice_multi_answer') {
163
+ const options = _.get(q, 'question_metadata.options', [])
164
+ const cleanedOptions = options.map((o, i) => ({ id: o.id || `option_${i}`, body: o.body || '' }))
165
+ const answerMeta = _.get(q, 'answer_metadata', {})
166
+ return {
167
+ ...base,
168
+ question_type: type,
169
+ question_metadata: { options: cleanedOptions },
170
+ answer_metadata: {
171
+ correct_id: answerMeta.correct_id,
172
+ correct_ids: answerMeta.correct_ids,
173
+ feedback: answerMeta.feedback || {},
174
+ },
175
+ }
176
+ }
177
+
178
+ if (type === 'matching_text') {
179
+ const prompts = _.get(q, 'question_metadata.prompts', []).map((p) => ({ id: p.id, body: p.body || '' }))
180
+ const choices = _.get(q, 'question_metadata.choices', []).map((c, i) => ({ id: c.id, letter_id: c.letter_id || String.fromCharCode(65 + i), body: c.body || '' }))
181
+ const map = _.get(q, 'answer_metadata.map', [])
182
+ return {
183
+ ...base,
184
+ question_type: type,
185
+ question_metadata: { prompts, choices },
186
+ answer_metadata: { map, feedback: _.get(q, 'answer_metadata.feedback', {}) },
187
+ }
188
+ }
189
+
190
+ if (type === 'ordering') {
191
+ const prompts = _.get(q, 'question_metadata.prompts', []).map((p) => ({ id: p.id, body: p.body || '' }))
192
+ const map = _.get(q, 'answer_metadata.map', [])
193
+ return {
194
+ ...base,
195
+ question_type: type,
196
+ question_metadata: { prompts },
197
+ answer_metadata: { map, feedback: _.get(q, 'answer_metadata.feedback', {}) },
198
+ }
199
+ }
200
+
201
+ if (type === 'fill_in_the_blank') {
202
+ const corrects = _.get(q, 'answer_metadata.correct_answer_options', [])
203
+ return {
204
+ ...base,
205
+ question_type: type,
206
+ question_metadata: _.get(q, 'question_metadata', {}),
207
+ answer_metadata: {
208
+ correct_id: _.get(q, 'answer_metadata.correct_id'),
209
+ correct_answer_options: corrects.map((c) => ({ id: c.id, answer: c.answer || '' })),
210
+ feedback: _.get(q, 'answer_metadata.feedback', {}),
211
+ },
212
+ }
213
+ }
214
+
215
+ return base
216
+ },
217
+ hasAnyExistingInput(snapshot) {
218
+ if (!snapshot) return false
219
+ if (snapshot.body && snapshot.body.replace(/<[^>]*>/g, '').trim().length > 0) return true
220
+ const qm = snapshot.question_metadata || {}
221
+ if (Array.isArray(qm.options)) {
222
+ return qm.options.some((o) => (o.body || '').replace(/<[^>]*>/g, '').trim().length > 0)
223
+ }
224
+ if (Array.isArray(qm.prompts)) {
225
+ return qm.prompts.some((p) => (p.body || '').replace(/<[^>]*>/g, '').trim().length > 0)
226
+ }
227
+ if (Array.isArray(qm.choices)) {
228
+ return qm.choices.some((c) => (c.body || '').replace(/<[^>]*>/g, '').trim().length > 0)
229
+ }
230
+ const am = snapshot.answer_metadata || {}
231
+ if (Array.isArray(am.correct_answer_options)) {
232
+ return am.correct_answer_options.some((c) => (c.answer || '').trim().length > 0)
233
+ }
234
+ if (am.feedback && typeof am.feedback.question === 'string' && am.feedback.question.trim().length > 0) {
235
+ return true
236
+ }
237
+ return false
238
+ },
119
239
  async generateAIQuestion() {
120
240
  this.isLoading = true
121
241
 
@@ -133,23 +253,64 @@ export default {
133
253
  // ASSESSMENT QUESTION GENERATION
134
254
  const assessment = new Assessment({ id: this.block.id })
135
255
 
136
- const responseBuilder = AssessmentQuestion.custom(
256
+ const endpointBuilder = AssessmentQuestion.custom(
137
257
  course,
138
258
  content,
139
259
  assessment,
140
260
  `suggest-questions`
141
- ).where('question_type', this.questionType)
261
+ )
262
+
263
+ let response
264
+ if (this.keepExisting) {
265
+ const existingInputs = this.buildExistingInputs()
266
+ const hasExisting = this.hasAnyExistingInput(existingInputs)
142
267
 
143
- // Apply blooms level filter if it's not 'None'
144
- if (this.selectedDifficulty !== 'None') {
145
- responseBuilder.where(
146
- 'blooms_level',
147
- this.selectedDifficulty
268
+ if (hasExisting) {
269
+ const payload = {
270
+ filter: {
271
+ question_type: this.questionType,
272
+ // Do not include blooms when preserving existing inputs
273
+ },
274
+ keep_existing_inputs: true,
275
+ existing_inputs: existingInputs,
276
+ }
277
+
278
+ response = await AssessmentQuestion.config({
279
+ method: 'POST',
280
+ data: payload,
281
+ })
282
+ .custom(course, content, assessment, `suggest-questions`)
283
+ .get()
284
+ } else {
285
+ // Treat as replace flow if no existing inputs
286
+ const builder = endpointBuilder.where(
287
+ 'question_type',
288
+ this.questionType
289
+ )
290
+ if (this.selectedDifficulty !== 'None') {
291
+ builder.where('blooms_level', this.selectedDifficulty)
292
+ }
293
+ response = await builder.get()
294
+ }
295
+ } else {
296
+ // Replace flow (legacy behavior)
297
+ // Explicitly clear all existing inputs before generating
298
+ try {
299
+ const cleared = this.buildClearedQuestion()
300
+ this.$emit('input', cleared)
301
+ } catch (e) {
302
+ // no-op if emit fails
303
+ }
304
+ const builder = endpointBuilder.where(
305
+ 'question_type',
306
+ this.questionType
148
307
  )
308
+ if (this.selectedDifficulty !== 'None') {
309
+ builder.where('blooms_level', this.selectedDifficulty)
310
+ }
311
+ response = await builder.get()
149
312
  }
150
313
 
151
- const response = await responseBuilder.get()
152
-
153
314
  if (response && response.length > 0) {
154
315
  const generatedQuestion = response[0]
155
316
 
@@ -196,6 +357,16 @@ export default {
196
357
  'An error occurred while generating content. Try generating again.'
197
358
  ) {
198
359
  errorText = this.$t(`${basePath}.character_limit`)
360
+ } else if (
361
+ (errorMessage === 'assessment.error.content_mismatch' ||
362
+ errorType === 'content_mismatch') &&
363
+ errorSubtype === 'INITIAL_INPUTS_NOT_RELATED'
364
+ ) {
365
+ // Inputs (question/correct answer) are unrelated to the course/page content
366
+ errorText =
367
+ this.$t(`${basePath}.inputs_unrelated`) +
368
+ '\\n\\n' +
369
+ this.$t(`${basePath}.inputs_unrelated_support`)
199
370
  } else if (
200
371
  errorType === 'insufficient_content' &&
201
372
  error.response?.data?.error?.details
@@ -1,35 +1,41 @@
1
1
  <template>
2
2
  <div>
3
3
  <v-row class="container-generate-ai mt-2">
4
- <v-col>{{
4
+ <v-col cols="12" sm="auto" class="d-flex align-center">{{
5
5
  $t('windward.integrations.components.llm.ai_assistance')
6
6
  }}</v-col>
7
- <v-col>
7
+ <v-col cols="12" sm>
8
8
  <ContentSelector
9
9
  v-model="selectedContent"
10
10
  :disabled="isLoading || disabled"
11
11
  ></ContentSelector>
12
12
  </v-col>
13
- <v-col>
13
+ <v-col cols="12" sm class="bloom-taxonomy-col">
14
14
  <BloomTaxonomySelector
15
15
  v-model="selectedDifficulty"
16
16
  :levels="taxonomyLevels"
17
17
  :advanced-levels="isMultipleChoiceType ? false : hasAdvancedBlooms"
18
18
  :disabled="isLoading || disabled"
19
- :hide-details="!isBucketGameType"
19
+ hide-details
20
20
  ></BloomTaxonomySelector>
21
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
- }}
22
+ <div class="caption-container">
23
+ <div
24
+ class="text-caption mt-1"
25
+ :style="{ visibility: isBucketGameType ? 'visible' : 'hidden' }"
26
+ >
27
+ {{
28
+ $t(
29
+ 'windward.integrations.components.llm.generate_content.generate_questions.replaces_content'
30
+ )
31
+ }}
32
+ </div>
28
33
  </div>
29
34
  </v-col>
30
35
  <v-col
31
36
  v-if="showReplaceExistingToggle"
32
- cols="auto"
37
+ cols="12"
38
+ sm="auto"
33
39
  class="d-flex align-center"
34
40
  >
35
41
  <v-switch
@@ -42,11 +48,11 @@
42
48
  :disabled="isLoading"
43
49
  ></v-switch>
44
50
  </v-col>
45
- <v-col>
51
+ <v-col cols="12" class="button-col">
46
52
  <v-btn
47
53
  elevation="0"
48
54
  color="secondary"
49
- class="mb-1 btn-selector"
55
+ class="btn-selector"
50
56
  :loading="isLoading"
51
57
  :disabled="isLoading"
52
58
  @click="generateAIQuestion"
@@ -542,4 +548,20 @@ export default {
542
548
  outline: 1px solid var(--v-secondary-base);
543
549
  border-radius: $border-radius-root;
544
550
  }
551
+ .bloom-taxonomy-col {
552
+ display: flex;
553
+ flex-direction: column;
554
+ }
555
+ .caption-container {
556
+ min-height: 28px;
557
+ display: flex;
558
+ align-items: flex-start;
559
+ }
560
+ .button-col {
561
+ display: flex;
562
+ align-items: flex-start;
563
+ }
564
+ .switch-col {
565
+ align-self: flex-start !important;
566
+ }
545
567
  </style>
@@ -0,0 +1,5 @@
1
+ import transformBlock from './transform_block'
2
+
3
+ export default {
4
+ transform_block: transformBlock,
5
+ }
@@ -0,0 +1,7 @@
1
+ export default {
2
+ change_block_type: 'Change block type',
3
+ change_block_type_insufficient_paragraphs:
4
+ 'Change block type is available when the text has at least two paragraphs.',
5
+ change_block_type_error:
6
+ 'Unable to change block type. Try again or edit manually.',
7
+ }
@@ -1,5 +1,7 @@
1
+ import actionPanel from './action_panel'
1
2
  import externalIntegration from './external_integration'
2
3
 
3
4
  export default {
4
5
  external_integration: externalIntegration,
6
+ action_panel: actionPanel,
5
7
  }
@@ -51,6 +51,10 @@ export default {
51
51
  content_mismatch_multiple_choice_support:
52
52
  'Consider adding clear expository text with distinct concepts, definitions, or examples that support question and distractor creation.',
53
53
 
54
+ inputs_unrelated: 'Your question or correct answer is unrelated to this page\'s content.',
55
+ inputs_unrelated_support:
56
+ 'Provide a question and/or correct answer that clearly relates to the selected page. Use terms, facts, or examples present on the page so the assistant can complete the remaining inputs.',
57
+
54
58
  character_limit:
55
59
  'An error occurred while generating content. Try generating again.',
56
60
  character_limit_detailed:
@@ -4,4 +4,5 @@ import fakeTextStream from './fake_text_stream'
4
4
  export default {
5
5
  generate_questions: generateQuestions,
6
6
  fake_text_stream: fakeTextStream,
7
+ keep_existing_inputs: 'Keep existing inputs',
7
8
  }
@@ -1,7 +1,6 @@
1
1
  export default {
2
2
  title: {
3
- lti_consumer: 'LTI Consumer',
4
- scorm_consumer: 'SCORM Consumer',
3
+ block_builder: 'Block Builder',
5
4
  ai_chat: 'AI Chat',
6
5
  },
7
6
  errors: {
@@ -0,0 +1,5 @@
1
+ import transformBlock from './transform_block'
2
+
3
+ export default {
4
+ transform_block: transformBlock,
5
+ }
@@ -0,0 +1,7 @@
1
+ export default {
2
+ change_block_type: 'Cambiar tipo de bloque',
3
+ change_block_type_insufficient_paragraphs:
4
+ 'Cambiar el tipo de bloque está disponible cuando el texto tiene al menos dos párrafos.',
5
+ change_block_type_error:
6
+ 'No se puede cambiar el tipo de bloque. Inténtalo de nuevo o edítalo manualmente.',
7
+ }
@@ -1,5 +1,7 @@
1
+ import actionPanel from './action_panel'
1
2
  import externalIntegration from './external_integration'
2
3
 
3
4
  export default {
4
5
  external_integration: externalIntegration,
6
+ action_panel: actionPanel,
5
7
  }
@@ -54,6 +54,11 @@ export default {
54
54
  content_mismatch_multiple_choice_support:
55
55
  'Considere agregar texto expositivo claro con conceptos, definiciones o ejemplos distintos que respalden la creación de preguntas y distractores.',
56
56
 
57
+ inputs_unrelated:
58
+ 'Tu pregunta o respuesta correcta no está relacionada con el contenido de esta página.',
59
+ inputs_unrelated_support:
60
+ 'Proporciona una pregunta y/o respuesta correcta que se relacione claramente con la página seleccionada. Usa términos, hechos o ejemplos presentes en la página para que el asistente complete el resto de los campos.',
61
+
57
62
  character_limit:
58
63
  'Se produjo un error al generar el contenido. Intenta generar de nuevo.',
59
64
  character_limit_detailed:
@@ -4,4 +4,5 @@ import fakeTextStream from './fake_text_stream'
4
4
  export default {
5
5
  generate_questions: generateQuestions,
6
6
  fake_text_stream: fakeTextStream,
7
+ keep_existing_inputs: 'Mantener entradas existentes',
7
8
  }
@@ -1,7 +1,6 @@
1
1
  export default {
2
2
  title: {
3
- lti_consumer: 'Consumidor LTI',
4
- scorm_consumer: 'Consumidor SCORM',
3
+ block_builder: 'Constructor de bloques',
5
4
  ai_chat: 'Chat de IA',
6
5
  },
7
6
  errors: {
@@ -0,0 +1,5 @@
1
+ import transformBlock from './transform_block'
2
+
3
+ export default {
4
+ transform_block: transformBlock,
5
+ }
@@ -0,0 +1,7 @@
1
+ export default {
2
+ change_block_type: 'Ändra blocktyp',
3
+ change_block_type_insufficient_paragraphs:
4
+ 'Ändra blocktyp är tillgängligt när texten har minst två stycken.',
5
+ change_block_type_error:
6
+ 'Kunde inte byta blocktyp. Försök igen eller ändra manuellt.',
7
+ }
@@ -1,5 +1,7 @@
1
+ import actionPanel from './action_panel'
1
2
  import externalIntegration from './external_integration'
2
3
 
3
4
  export default {
4
5
  external_integration: externalIntegration,
6
+ action_panel: actionPanel,
5
7
  }
@@ -52,6 +52,11 @@ export default {
52
52
  content_mismatch_multiple_choice_support:
53
53
  'Överväg att lägga till tydlig, förklarande text med distinkta koncept, definitioner eller exempel som stödjer skapandet av frågor och distraktorer.',
54
54
 
55
+ inputs_unrelated:
56
+ 'Din fråga eller korrekta svar är inte relaterade till innehållet på denna sida.',
57
+ inputs_unrelated_support:
58
+ 'Ange en fråga och/eller ett korrekt svar som tydligt relaterar till den valda sidan. Använd termer, fakta eller exempel som finns på sidan så att assistenten kan fylla i återstående fält.',
59
+
55
60
  character_limit:
56
61
  'Ett fel uppstod när innehållet genererades. Försök generera igen.',
57
62
  character_limit_detailed:
@@ -4,4 +4,5 @@ import fakeTextStream from './fake_text_stream'
4
4
  export default {
5
5
  generate_questions: generateQuestions,
6
6
  fake_text_stream: fakeTextStream,
7
+ keep_existing_inputs: 'Behåll befintliga indata',
7
8
  }
@@ -1,7 +1,6 @@
1
1
  export default {
2
2
  title: {
3
- lti_consumer: 'LTI konsument',
4
- scorm_consumer: 'SCORM-konsument',
3
+ block_builder: 'Blockbyggare',
5
4
  ai_chat: 'AI-chatt',
6
5
  },
7
6
  errors: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windward/integrations",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "description": "Windward UI Plugin Integrations for 3rd Party Systems",
5
5
  "main": "plugin.js",
6
6
  "scripts": {
package/plugin.js CHANGED
@@ -14,9 +14,11 @@ import ExternalIntegrationScormHelper from './helpers/ExternalIntegration/ScormH
14
14
  import LtiConsumerBlock from './components/Content/Blocks/ExternalIntegration/LtiConsumer'
15
15
  import LtiConsumerBlockSettings from './components/Settings/ExternalIntegration/LtiConsumerSettings'
16
16
 
17
- import ScormConsumerBlock from './components/Content/Blocks/ExternalIntegration/ScormConsumer'
17
+ // import ScormConsumerBlock from './components/Content/Blocks/ExternalIntegration/ScormConsumer'
18
18
  import ScormConsumerBlockSettings from './components/Settings/ExternalIntegration/ScormConsumerSettings'
19
19
 
20
+ import ActionPanelTransformBlock from './components/Content/Blocks/ActionPanel/TransformBlock.vue'
21
+
20
22
  import ManageCourseIntegrationSettings from './components/Settings/ExternalIntegration/ManageCourseIntegrationSettings'
21
23
 
22
24
  import FileImportMenu from './components/FileImport/FileImportMenu.vue'
@@ -279,7 +281,8 @@ export default {
279
281
  'windward.integrations.shared.content_blocks.grouping.integrations',
280
282
  },
281
283
  },
282
- /*{
284
+ /*
285
+ {
283
286
  tag: 'windward-integrations-scorm-consumer',
284
287
  template: ScormConsumerBlock,
285
288
  metadata: {
@@ -288,7 +291,8 @@ export default {
288
291
  grouping:
289
292
  'windward.integrations.shared.content_blocks.grouping.integrations',
290
293
  },
291
- },*/
294
+ },
295
+ */
292
296
  ],
293
297
  contentBlockSetting: [
294
298
  {
@@ -297,7 +301,7 @@ export default {
297
301
  context: ['block.windward-integrations-lti-consumer'],
298
302
  metadata: {
299
303
  icon: 'mdi-cog',
300
- name: 'windward.integrations.shared.settings.title.lti_consumer',
304
+ name: 'windward.integrations.shared.settings.title.block_builder',
301
305
  },
302
306
  },
303
307
  {
@@ -306,10 +310,17 @@ export default {
306
310
  context: ['block.windward-integrations-scorm-consumer'],
307
311
  metadata: {
308
312
  icon: 'mdi-cog',
309
- name: 'windward.integrations.shared.settings.title.scorm_consumer',
313
+ name: 'windward.integrations.shared.settings.title.block_builder',
310
314
  },
311
315
  },
312
316
  ],
317
+ contentBlockActionPanel: [
318
+ {
319
+ tag: 'windward-integrations-action-panel-transform-block',
320
+ template: ActionPanelTransformBlock,
321
+ context: ['block.content-blocks-text'],
322
+ },
323
+ ],
313
324
  courseSetting: [
314
325
  {
315
326
  tag: 'windward-integrations-external-integration-settings',