@windward/integrations 0.24.0 → 0.25.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 +6 -0
- package/components/Integration/AiAgentIntegration/ChatWindow.vue +199 -28
- package/components/LLM/GenerateContent/AssessmentQuestionGenerateButton.vue +2 -1
- package/i18n/ar-SA/components/content/blocks/action_panel/index.ts +2 -0
- package/i18n/ar-SA/components/content/blocks/action_panel/transform_activity.ts +7 -0
- package/i18n/ar-SA/components/content/blocks/external_integration/lti_consumer.ts +1 -0
- package/i18n/ar-SA/components/settings/external_integration/lti_consumer.ts +2 -0
- package/i18n/fr-FR/components/content/blocks/action_panel/index.ts +2 -0
- package/i18n/fr-FR/components/content/blocks/action_panel/transform_activity.ts +7 -0
- package/i18n/fr-FR/components/content/blocks/external_integration/lti_consumer.ts +1 -0
- package/i18n/fr-FR/components/settings/external_integration/lti_consumer.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Release [0.25.0] - 2026-03-30
|
|
4
|
+
|
|
5
|
+
* Merged in feature/LE-2279/review-course (pull request #137)
|
|
6
|
+
* Merge remote-tracking branch 'origin/release/0.25.0' into feature/LE-2279/review-course
|
|
7
|
+
|
|
8
|
+
|
|
3
9
|
## Release [0.24.0] - 2026-03-10
|
|
4
10
|
|
|
5
11
|
* Merged in feature/LE-2240-organization-sales-transaction-p (pull request #136)
|
|
@@ -105,7 +105,12 @@
|
|
|
105
105
|
</div>
|
|
106
106
|
|
|
107
107
|
<!-- Messages Area -->
|
|
108
|
-
<div
|
|
108
|
+
<div
|
|
109
|
+
ref="messagesScroll"
|
|
110
|
+
class="messages"
|
|
111
|
+
@scroll="onMessagesScroll"
|
|
112
|
+
@click="onMessageLinkClick"
|
|
113
|
+
>
|
|
109
114
|
<div
|
|
110
115
|
v-if="loadingOlder"
|
|
111
116
|
class="top-loader d-flex justify-center mb-2"
|
|
@@ -127,7 +132,7 @@
|
|
|
127
132
|
<div v-else>
|
|
128
133
|
<div
|
|
129
134
|
v-for="(msg, i) in messages"
|
|
130
|
-
:key="i"
|
|
135
|
+
:key="messageKey(msg, i)"
|
|
131
136
|
class="message"
|
|
132
137
|
:class="msg.role"
|
|
133
138
|
>
|
|
@@ -268,11 +273,12 @@
|
|
|
268
273
|
import _ from 'lodash'
|
|
269
274
|
import { mapGetters } from 'vuex'
|
|
270
275
|
|
|
271
|
-
import AgentChat from '../../../models/AgentChat'
|
|
272
|
-
import AgentChatMessage from '../../../models/AgentChatMessage'
|
|
273
276
|
import Organization from '~/models/Organization'
|
|
274
277
|
import Course from '~/models/Course'
|
|
278
|
+
import Uuid from '~/helpers/Uuid'
|
|
275
279
|
import TextViewer from '~/components/Text/TextViewer.vue'
|
|
280
|
+
import AgentChat from '../../../models/AgentChat'
|
|
281
|
+
import AgentChatMessage from '../../../models/AgentChatMessage'
|
|
276
282
|
|
|
277
283
|
export default {
|
|
278
284
|
name: 'ChatWindow',
|
|
@@ -312,7 +318,7 @@ export default {
|
|
|
312
318
|
)
|
|
313
319
|
const quotes = []
|
|
314
320
|
|
|
315
|
-
for (const
|
|
321
|
+
for (const quote of Object.values(localizedQuotes)) {
|
|
316
322
|
quotes.push(quote)
|
|
317
323
|
}
|
|
318
324
|
|
|
@@ -365,6 +371,68 @@ export default {
|
|
|
365
371
|
})
|
|
366
372
|
},
|
|
367
373
|
methods: {
|
|
374
|
+
// Intercept internal links rendered inside AI message content so we can
|
|
375
|
+
// navigate within the SPA and preserve block focus query params.
|
|
376
|
+
async onMessageLinkClick(event) {
|
|
377
|
+
const target = event.target
|
|
378
|
+
if (!(target instanceof Element)) return
|
|
379
|
+
|
|
380
|
+
// Only handle anchor clicks from the rendered message body.
|
|
381
|
+
const anchor = target.closest('a')
|
|
382
|
+
if (!anchor) return
|
|
383
|
+
|
|
384
|
+
if (
|
|
385
|
+
event.defaultPrevented ||
|
|
386
|
+
event.button !== 0 ||
|
|
387
|
+
event.metaKey ||
|
|
388
|
+
event.ctrlKey ||
|
|
389
|
+
event.shiftKey ||
|
|
390
|
+
event.altKey ||
|
|
391
|
+
anchor.target === '_blank' ||
|
|
392
|
+
anchor.hasAttribute('download')
|
|
393
|
+
) {
|
|
394
|
+
return
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const href = anchor.getAttribute('href')
|
|
398
|
+
if (!href) return
|
|
399
|
+
|
|
400
|
+
let url
|
|
401
|
+
try {
|
|
402
|
+
url = new URL(href, window.location.href)
|
|
403
|
+
} catch (e) {
|
|
404
|
+
return
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (url.origin !== window.location.origin) {
|
|
408
|
+
return
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const route = this.routeFromUrl(url, href)
|
|
412
|
+
if (!route) {
|
|
413
|
+
return
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
event.preventDefault()
|
|
417
|
+
|
|
418
|
+
if (
|
|
419
|
+
route.contentId &&
|
|
420
|
+
route.courseId === this.$route.params.course &&
|
|
421
|
+
route.sectionId === this.$route.params.section
|
|
422
|
+
) {
|
|
423
|
+
// Reuse the in-page content loader when the link stays within
|
|
424
|
+
// the current course section so focus state updates without a
|
|
425
|
+
// full route push.
|
|
426
|
+
await this.$ContentService.set(route.contentId, {}, route.query)
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
await this.$router.push({
|
|
431
|
+
path: route.path,
|
|
432
|
+
query: route.query,
|
|
433
|
+
hash: route.hash,
|
|
434
|
+
})
|
|
435
|
+
},
|
|
368
436
|
clearError() {
|
|
369
437
|
this.sendError = null
|
|
370
438
|
this.lastFailedPrompt = ''
|
|
@@ -397,8 +465,8 @@ export default {
|
|
|
397
465
|
// clear any previous error
|
|
398
466
|
this.sendError = null
|
|
399
467
|
this.lastFailedPrompt = ''
|
|
400
|
-
// Show the user's message optimistically
|
|
401
468
|
this.messages.push({
|
|
469
|
+
id: this.temporaryMessageId(),
|
|
402
470
|
role: 'user',
|
|
403
471
|
text: userText,
|
|
404
472
|
created_at: Date.now(),
|
|
@@ -442,7 +510,8 @@ export default {
|
|
|
442
510
|
)
|
|
443
511
|
.save()
|
|
444
512
|
}
|
|
445
|
-
|
|
513
|
+
this.currentSessionId = reply.id
|
|
514
|
+
await this.reloadLatestMessages()
|
|
446
515
|
this.awaitingReply = false
|
|
447
516
|
this.showTypingDots = false
|
|
448
517
|
this.showProcessingMessage = false
|
|
@@ -451,9 +520,6 @@ export default {
|
|
|
451
520
|
if (this.longWaitTimeoutId) clearTimeout(this.longWaitTimeoutId)
|
|
452
521
|
if (this.feedbackIntervalId)
|
|
453
522
|
clearInterval(this.feedbackIntervalId)
|
|
454
|
-
// set current session and refresh messages (latest page)
|
|
455
|
-
this.currentSessionId = reply.id
|
|
456
|
-
await this.reloadLatestMessages()
|
|
457
523
|
this.$nextTick(this.scrollToBottom)
|
|
458
524
|
// persist current session on each send
|
|
459
525
|
this.persistCurrentSession(reply.id)
|
|
@@ -469,7 +535,6 @@ export default {
|
|
|
469
535
|
if (this.feedbackIntervalId)
|
|
470
536
|
clearInterval(this.feedbackIntervalId)
|
|
471
537
|
|
|
472
|
-
// Remove the last optimistic user message to avoid duplicates on retry
|
|
473
538
|
const last = this.messages[this.messages.length - 1]
|
|
474
539
|
if (last && last.role === 'user' && last.text === userText) {
|
|
475
540
|
this.messages.pop()
|
|
@@ -628,23 +693,34 @@ export default {
|
|
|
628
693
|
return 0
|
|
629
694
|
},
|
|
630
695
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
696
|
+
messageKey(msg, index) {
|
|
697
|
+
return this.getMessageId(msg) || `message-${index}`
|
|
698
|
+
},
|
|
699
|
+
|
|
700
|
+
temporaryMessageId() {
|
|
701
|
+
return `temp-${Date.now()}-${Math.random()
|
|
702
|
+
.toString(36)
|
|
703
|
+
.slice(2, 10)}`
|
|
704
|
+
},
|
|
705
|
+
|
|
706
|
+
getMessageId(msg) {
|
|
707
|
+
if (msg == null || msg.id == null) return ''
|
|
708
|
+
return String(msg.id)
|
|
636
709
|
},
|
|
637
710
|
|
|
638
711
|
normalizeMessages(list) {
|
|
639
712
|
if (!Array.isArray(list)) return []
|
|
640
|
-
return list
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
713
|
+
return list.map((m) => {
|
|
714
|
+
const role =
|
|
715
|
+
m.role || (m.sender === 'user' ? 'user' : 'assistant')
|
|
716
|
+
|
|
717
|
+
return {
|
|
718
|
+
...m,
|
|
719
|
+
id: this.getMessageId(m),
|
|
720
|
+
role,
|
|
721
|
+
created_at: m.created_at ?? m.createdAt,
|
|
722
|
+
}
|
|
723
|
+
})
|
|
648
724
|
},
|
|
649
725
|
|
|
650
726
|
senderLabel(msg) {
|
|
@@ -724,12 +800,9 @@ export default {
|
|
|
724
800
|
return Array.isArray(results) ? results : []
|
|
725
801
|
},
|
|
726
802
|
async reloadLatestMessages() {
|
|
727
|
-
// Start from newest page (desc page 1)
|
|
728
803
|
this.resetPaginationState()
|
|
729
804
|
const data = await this.fetchMessagesPage(1)
|
|
730
|
-
|
|
731
|
-
const normalized = this.normalizeMessages(data)
|
|
732
|
-
this.messages = normalized
|
|
805
|
+
this.messages = this.normalizeMessages(data)
|
|
733
806
|
this.nextPage = 2
|
|
734
807
|
// If fewer than perPage, no more older
|
|
735
808
|
if (!data || data.length < this.perPage) this.noMoreOlder = true
|
|
@@ -767,6 +840,104 @@ export default {
|
|
|
767
840
|
this.loadOlder()
|
|
768
841
|
}
|
|
769
842
|
},
|
|
843
|
+
buildCourseContentRoute(contentId, query = {}, hash = undefined) {
|
|
844
|
+
const courseId = _.get(this.$route, 'params.course', null)
|
|
845
|
+
const sectionId = _.get(this.$route, 'params.section', null)
|
|
846
|
+
|
|
847
|
+
if (!courseId || !sectionId || !Uuid.test(contentId)) {
|
|
848
|
+
return null
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
return {
|
|
852
|
+
path: `/course/${courseId}/section/${sectionId}/content/${contentId}`,
|
|
853
|
+
query,
|
|
854
|
+
hash,
|
|
855
|
+
courseId,
|
|
856
|
+
sectionId,
|
|
857
|
+
contentId,
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
routeFromRelativeHref(href, query = {}, hash = undefined) {
|
|
861
|
+
if (typeof href !== 'string') {
|
|
862
|
+
return null
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const trimmedHref = href.trim()
|
|
866
|
+
if (!trimmedHref) {
|
|
867
|
+
return null
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const contentId = trimmedHref
|
|
871
|
+
.replace(/^\.\//, '')
|
|
872
|
+
.split(/[?#]/, 1)[0]
|
|
873
|
+
|
|
874
|
+
if (
|
|
875
|
+
!contentId ||
|
|
876
|
+
trimmedHref.startsWith('/') ||
|
|
877
|
+
!Uuid.test(contentId)
|
|
878
|
+
) {
|
|
879
|
+
return null
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
return this.buildCourseContentRoute(contentId, query, hash)
|
|
883
|
+
},
|
|
884
|
+
routeFromUrl(url, href = '') {
|
|
885
|
+
const query = {}
|
|
886
|
+
url.searchParams.forEach((value, key) => {
|
|
887
|
+
query[key] = value
|
|
888
|
+
})
|
|
889
|
+
|
|
890
|
+
const relativeRoute = this.routeFromRelativeHref(
|
|
891
|
+
href,
|
|
892
|
+
query,
|
|
893
|
+
url.hash || undefined
|
|
894
|
+
)
|
|
895
|
+
if (relativeRoute) {
|
|
896
|
+
return relativeRoute
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const path = url.pathname
|
|
900
|
+
if (!path.startsWith('/course/')) {
|
|
901
|
+
return null
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const segments = path.split('/').filter(Boolean)
|
|
905
|
+
const courseIndex = segments.indexOf('course')
|
|
906
|
+
const sectionIndex = segments.indexOf('section')
|
|
907
|
+
const contentIndex = segments.indexOf('content')
|
|
908
|
+
|
|
909
|
+
if (courseIndex === -1 || sectionIndex === -1) {
|
|
910
|
+
return null
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (contentIndex === -1) {
|
|
914
|
+
const currentSectionId = _.get(this.$route, 'params.section')
|
|
915
|
+
const possibleContentId = segments[sectionIndex + 1] || ''
|
|
916
|
+
|
|
917
|
+
if (
|
|
918
|
+
currentSectionId &&
|
|
919
|
+
possibleContentId !== currentSectionId &&
|
|
920
|
+
Uuid.test(possibleContentId)
|
|
921
|
+
) {
|
|
922
|
+
return this.buildCourseContentRoute(
|
|
923
|
+
possibleContentId,
|
|
924
|
+
query,
|
|
925
|
+
url.hash || undefined
|
|
926
|
+
)
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
return null
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
return {
|
|
933
|
+
path,
|
|
934
|
+
query,
|
|
935
|
+
hash: url.hash || undefined,
|
|
936
|
+
courseId: segments[courseIndex + 1] || null,
|
|
937
|
+
sectionId: segments[sectionIndex + 1] || null,
|
|
938
|
+
contentId: segments[contentIndex + 1] || null,
|
|
939
|
+
}
|
|
940
|
+
},
|
|
770
941
|
},
|
|
771
942
|
}
|
|
772
943
|
</script>
|
|
@@ -261,7 +261,8 @@ export default {
|
|
|
261
261
|
)
|
|
262
262
|
|
|
263
263
|
let response
|
|
264
|
-
|
|
264
|
+
const allowKeepExisting = this.questionType !== 'true_false'
|
|
265
|
+
if (this.keepExisting && allowKeepExisting) {
|
|
265
266
|
const existingInputs = this.buildExistingInputs()
|
|
266
267
|
const hasExisting = this.hasAnyExistingInput(existingInputs)
|
|
267
268
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
change_block_type: 'Change block type',
|
|
3
|
+
change_block_type_insufficient_items:
|
|
4
|
+
'Change block type is available when the activity has at least two items.',
|
|
5
|
+
change_block_type_error:
|
|
6
|
+
'Unable to change block type. Try again or edit manually.',
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
change_block_type: 'Change block type',
|
|
3
|
+
change_block_type_insufficient_items:
|
|
4
|
+
'Change block type is available when the activity has at least two items.',
|
|
5
|
+
change_block_type_error:
|
|
6
|
+
'Unable to change block type. Try again or edit manually.',
|
|
7
|
+
}
|