@windward/integrations 0.16.0 → 0.17.0

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