@intellectif/lk-core 1.0.0 → 1.2.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 +30 -0
- package/README.md +2 -1
- package/dist/{activity-DgpBJeBc.d.cts → activity-D705CXng.d.cts} +439 -7
- package/dist/{activity-DgpBJeBc.d.ts → activity-D705CXng.d.ts} +439 -7
- package/dist/ai-check.cjs +300 -9
- package/dist/ai-check.cjs.map +1 -1
- package/dist/ai-check.d.cts +18 -3
- package/dist/ai-check.d.ts +18 -3
- package/dist/ai-check.js +295 -4
- package/dist/ai-check.js.map +1 -1
- package/dist/{chunk-TJJFKKH2.cjs → chunk-46BXIGLG.cjs} +4 -4
- package/dist/{chunk-TJJFKKH2.cjs.map → chunk-46BXIGLG.cjs.map} +1 -1
- package/dist/{chunk-VSH7ECQL.cjs → chunk-4YANYTFN.cjs} +43 -43
- package/dist/{chunk-VSH7ECQL.cjs.map → chunk-4YANYTFN.cjs.map} +1 -1
- package/dist/chunk-7KOWU4RO.js +2207 -0
- package/dist/chunk-7KOWU4RO.js.map +1 -0
- package/dist/{chunk-DSDXL3JO.cjs → chunk-7THXD7AN.cjs} +657 -197
- package/dist/chunk-7THXD7AN.cjs.map +1 -0
- package/dist/chunk-CYCTIXUN.cjs +118 -0
- package/dist/chunk-CYCTIXUN.cjs.map +1 -0
- package/dist/chunk-CYPA5BW5.js +118 -0
- package/dist/chunk-CYPA5BW5.js.map +1 -0
- package/dist/{chunk-SJVRUPJT.js → chunk-JTNDIBZO.js} +2 -2
- package/dist/{chunk-XS6U2ARZ.js → chunk-MV2N2WNS.js} +6 -112
- package/dist/chunk-MV2N2WNS.js.map +1 -0
- package/dist/chunk-MXI26H62.cjs +2207 -0
- package/dist/chunk-MXI26H62.cjs.map +1 -0
- package/dist/{chunk-FBKTION6.js → chunk-QS7B42ZR.js} +640 -180
- package/dist/chunk-QS7B42ZR.js.map +1 -0
- package/dist/{chunk-IARLJ5MW.js → chunk-QX2ERL72.js} +2 -2
- package/dist/{chunk-2JT2WUQ2.cjs → chunk-ZWXQSKCX.cjs} +14 -120
- package/dist/chunk-ZWXQSKCX.cjs.map +1 -0
- package/dist/{index-BVNeSCWC.d.ts → index-BChYtrEe.d.ts} +1 -1
- package/dist/{index-BlOLlwOi.d.cts → index-VXt_v3YI.d.cts} +1 -1
- package/dist/index.cjs +76 -593
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +308 -66
- package/dist/index.d.ts +308 -66
- package/dist/index.js +76 -593
- package/dist/index.js.map +1 -1
- package/dist/schemas.cjs +5 -3
- package/dist/schemas.cjs.map +1 -1
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +5 -3
- package/dist/scoring.cjs +3 -3
- package/dist/scoring.d.cts +242 -242
- package/dist/scoring.d.ts +242 -242
- package/dist/scoring.js +2 -2
- package/dist/xapi.cjs +3 -3
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-2JT2WUQ2.cjs.map +0 -1
- package/dist/chunk-DSDXL3JO.cjs.map +0 -1
- package/dist/chunk-FBKTION6.js.map +0 -1
- package/dist/chunk-RCZCP5OE.js +0 -729
- package/dist/chunk-RCZCP5OE.js.map +0 -1
- package/dist/chunk-W5KB3DNT.cjs +0 -729
- package/dist/chunk-W5KB3DNT.cjs.map +0 -1
- package/dist/chunk-XS6U2ARZ.js.map +0 -1
- /package/dist/{chunk-SJVRUPJT.js.map → chunk-JTNDIBZO.js.map} +0 -0
- /package/dist/{chunk-IARLJ5MW.js.map → chunk-QX2ERL72.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -4,49 +4,63 @@ import {
|
|
|
4
4
|
validateXAPIStatement,
|
|
5
5
|
xAPIBuilder,
|
|
6
6
|
xapiDefinitionFor
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-JTNDIBZO.js";
|
|
8
8
|
import {
|
|
9
|
-
INTERACTIVE_VIDEO_ITEM_TYPES,
|
|
10
|
-
ItemGroupSchema,
|
|
11
|
-
RedactedItemGroupSchema,
|
|
12
|
-
TIMELINE_MAX_CHAPTERS,
|
|
13
|
-
TIMELINE_MAX_ITEMS,
|
|
14
|
-
TIMELINE_MAX_QUIZZES,
|
|
15
|
-
TIMELINE_MAX_TITLE_LENGTH,
|
|
16
9
|
dictationJsonSchema,
|
|
17
10
|
fillInTheBlanksJsonSchema,
|
|
18
11
|
gapSelectJsonSchema,
|
|
19
|
-
isInteractiveVideoItemType,
|
|
20
12
|
itemGroupJsonSchema,
|
|
21
13
|
jsonSchemaFor,
|
|
22
14
|
multipleChoiceJsonSchema,
|
|
23
15
|
readAloudJsonSchema,
|
|
24
16
|
stimulusJsonSchema,
|
|
25
17
|
validateActivity,
|
|
26
|
-
validateItemGroup,
|
|
27
18
|
validateMedia,
|
|
28
19
|
validateOptionMedia,
|
|
29
20
|
writtenResponseJsonSchema
|
|
30
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-CYPA5BW5.js";
|
|
31
22
|
import {
|
|
23
|
+
AI_COACHING_MAX_TIP_LENGTH,
|
|
24
|
+
AI_COACHING_MAX_WORDS,
|
|
25
|
+
AI_CRITIQUE_MAX_FIELD_LENGTH,
|
|
26
|
+
AI_CRITIQUE_MAX_FINDINGS,
|
|
27
|
+
AI_DRAFTS_MAX_COUNT,
|
|
28
|
+
AI_DRAFTS_MAX_REPAIRS,
|
|
32
29
|
AI_EXPLANATION_TYPES,
|
|
33
30
|
AI_HINT_TYPES,
|
|
34
31
|
AI_TEXT_MAX_LENGTH,
|
|
35
32
|
AI_WRITING_MAX_CORRECTIONS,
|
|
36
33
|
AI_WRITING_MAX_FIELD_LENGTH,
|
|
37
34
|
aiAllowedByContent,
|
|
35
|
+
aiCoachingRequest,
|
|
36
|
+
aiCritiqueRequest,
|
|
37
|
+
aiDraftsRepairRequest,
|
|
38
|
+
aiDraftsRequest,
|
|
38
39
|
aiExplanationRequest,
|
|
39
40
|
aiGradeOf,
|
|
40
41
|
aiHintRequest,
|
|
41
42
|
aiSupports,
|
|
42
43
|
aiWritingFeedbackRequest,
|
|
43
44
|
buildAiFacts,
|
|
45
|
+
checkAiCoaching,
|
|
46
|
+
checkAiCritique,
|
|
47
|
+
checkAiDrafts,
|
|
44
48
|
checkAiExplanation,
|
|
45
49
|
checkAiHint,
|
|
46
50
|
checkAiWritingFeedback,
|
|
51
|
+
createDraft,
|
|
52
|
+
createInteractiveVideoDraft,
|
|
53
|
+
createItemGroupDraft,
|
|
54
|
+
critiqueDraft,
|
|
55
|
+
critiqueDrafts,
|
|
56
|
+
critiqueItemGroupDraft,
|
|
57
|
+
generateDrafts,
|
|
47
58
|
hintRevealsAnswer,
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
interactiveVideoFromDrafts,
|
|
60
|
+
readAiTextResult,
|
|
61
|
+
validateDraft,
|
|
62
|
+
validateItemGroupDraft
|
|
63
|
+
} from "./chunk-7KOWU4RO.js";
|
|
50
64
|
import {
|
|
51
65
|
DEFAULT_ITEM_SCORING_POLICY,
|
|
52
66
|
DEFAULT_PASS_THRESHOLD,
|
|
@@ -71,7 +85,17 @@ import {
|
|
|
71
85
|
scoreTries,
|
|
72
86
|
validateItemScoringPolicy,
|
|
73
87
|
validateSpeechAssessment
|
|
74
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-QX2ERL72.js";
|
|
89
|
+
import {
|
|
90
|
+
INTERACTIVE_VIDEO_ITEM_TYPES,
|
|
91
|
+
RedactedItemGroupSchema,
|
|
92
|
+
TIMELINE_MAX_CHAPTERS,
|
|
93
|
+
TIMELINE_MAX_ITEMS,
|
|
94
|
+
TIMELINE_MAX_QUIZZES,
|
|
95
|
+
TIMELINE_MAX_TITLE_LENGTH,
|
|
96
|
+
isInteractiveVideoItemType,
|
|
97
|
+
validateItemGroup
|
|
98
|
+
} from "./chunk-MV2N2WNS.js";
|
|
75
99
|
import {
|
|
76
100
|
ActivitySchemaError,
|
|
77
101
|
DICTATION_MAX_ACCEPTED_TRANSCRIPTS,
|
|
@@ -79,9 +103,7 @@ import {
|
|
|
79
103
|
DICTATION_MAX_EQUIVALENCE_LENGTH,
|
|
80
104
|
DICTATION_MAX_TEXT_LENGTH,
|
|
81
105
|
DICTATION_MAX_TRANSCRIPT_LENGTH,
|
|
82
|
-
DRAFT_ISSUE_SEVERITY,
|
|
83
106
|
DeferredScoringError,
|
|
84
|
-
GROUP_CAPTIONS_REVEAL_DICTATION,
|
|
85
107
|
MEDIA_FIELD_POLICY,
|
|
86
108
|
READ_ALOUD_MAX_DIMENSION_WEIGHT,
|
|
87
109
|
READ_ALOUD_MAX_REFERENCE_LENGTH,
|
|
@@ -91,34 +113,22 @@ import {
|
|
|
91
113
|
SPEECH_ASSESSMENT_MAX_WORDS,
|
|
92
114
|
UnknownActivityTypeError,
|
|
93
115
|
alignDictation,
|
|
94
|
-
checkMedia,
|
|
95
116
|
countWords,
|
|
96
|
-
coveredPathsOf,
|
|
97
|
-
covers,
|
|
98
117
|
defineActivityType,
|
|
99
118
|
dictationReferenceWords,
|
|
100
119
|
dictationType,
|
|
101
120
|
fillInTheBlanksType,
|
|
102
121
|
gapSelectType,
|
|
103
122
|
getActivityTypeDescriptor,
|
|
104
|
-
groupCaptionsField,
|
|
105
|
-
isMissingId,
|
|
106
|
-
isRecord,
|
|
107
|
-
isUnset,
|
|
108
|
-
isUnwritten,
|
|
109
|
-
issue,
|
|
110
123
|
levenshteinDistance,
|
|
111
124
|
matchText,
|
|
112
125
|
multipleChoiceType,
|
|
113
|
-
pathKey,
|
|
114
126
|
readAloudType,
|
|
115
127
|
readSchema,
|
|
116
|
-
refusesEmpty,
|
|
117
128
|
registerActivityType,
|
|
118
129
|
registeredActivityTypes,
|
|
119
|
-
withValidationScope,
|
|
120
130
|
writtenResponseType
|
|
121
|
-
} from "./chunk-
|
|
131
|
+
} from "./chunk-QS7B42ZR.js";
|
|
122
132
|
|
|
123
133
|
// src/content-hash.ts
|
|
124
134
|
function canonicalJson(value, seen = /* @__PURE__ */ new Set()) {
|
|
@@ -809,551 +819,6 @@ function diffResponses(before, after) {
|
|
|
809
819
|
return entries;
|
|
810
820
|
}
|
|
811
821
|
|
|
812
|
-
// src/authoring/item-group.ts
|
|
813
|
-
function createItemGroupDraft(context) {
|
|
814
|
-
const issued = /* @__PURE__ */ new Set();
|
|
815
|
-
const newId = () => {
|
|
816
|
-
const id = context.newId();
|
|
817
|
-
if (typeof id !== "string" || id === "") {
|
|
818
|
-
throw new Error("createItemGroupDraft: newId() must return a non-empty string.");
|
|
819
|
-
}
|
|
820
|
-
if (issued.has(id)) {
|
|
821
|
-
throw new Error(
|
|
822
|
-
`createItemGroupDraft: newId() returned "${id}" twice. Ids within one draft must differ.`
|
|
823
|
-
);
|
|
824
|
-
}
|
|
825
|
-
issued.add(id);
|
|
826
|
-
return id;
|
|
827
|
-
};
|
|
828
|
-
return {
|
|
829
|
-
schemaVersion: "1.0",
|
|
830
|
-
type: "item-group",
|
|
831
|
-
id: newId(),
|
|
832
|
-
stimulus: { id: newId(), kind: "text", body: "" },
|
|
833
|
-
items: []
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
function createInteractiveVideoDraft(context) {
|
|
837
|
-
const draft = createItemGroupDraft(context);
|
|
838
|
-
return {
|
|
839
|
-
...draft,
|
|
840
|
-
stimulus: { id: draft.stimulus.id, kind: "video", media: { type: "video", url: "" } },
|
|
841
|
-
timeline: { cues: [] }
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
function validateItemGroupDraft(draft) {
|
|
845
|
-
return withValidationScope(() => validateItemGroupDraftInScope(draft));
|
|
846
|
-
}
|
|
847
|
-
function validateItemGroupDraftInScope(draft) {
|
|
848
|
-
const issues = [];
|
|
849
|
-
if (!isRecord(draft)) {
|
|
850
|
-
issues.push(
|
|
851
|
-
issue("ig_not_an_object", [], "A group must be an object with a stimulus and its items.")
|
|
852
|
-
);
|
|
853
|
-
return { status: "invalid", issues };
|
|
854
|
-
}
|
|
855
|
-
issues.push(...checkGroupIdentity(draft));
|
|
856
|
-
if (isRecord(draft.stimulus)) {
|
|
857
|
-
issues.push(...checkStimulus(draft.stimulus));
|
|
858
|
-
} else if (isUnset(draft.stimulus)) {
|
|
859
|
-
issues.push(
|
|
860
|
-
issue(
|
|
861
|
-
"ig_stimulus_required",
|
|
862
|
-
["stimulus"],
|
|
863
|
-
"Add the passage, recording or image the questions are about."
|
|
864
|
-
)
|
|
865
|
-
);
|
|
866
|
-
}
|
|
867
|
-
issues.push(...checkItems(draft));
|
|
868
|
-
if (draft.timeline !== void 0 && draft.timeline !== null) {
|
|
869
|
-
issues.push(...checkTimeline(draft));
|
|
870
|
-
}
|
|
871
|
-
const captions = groupCaptionsField(draft, (captionsUrl) => !isUnwritten(captionsUrl));
|
|
872
|
-
if (captions !== void 0) {
|
|
873
|
-
issues.push(
|
|
874
|
-
issue(
|
|
875
|
-
"dc_captions_not_allowed",
|
|
876
|
-
["stimulus", "media", captions],
|
|
877
|
-
GROUP_CAPTIONS_REVEAL_DICTATION
|
|
878
|
-
)
|
|
879
|
-
);
|
|
880
|
-
}
|
|
881
|
-
const parsed = ItemGroupSchema.safeParse(draft, { reportInput: true });
|
|
882
|
-
if (!parsed.success) {
|
|
883
|
-
for (const schemaIssue of parsed.error.issues) {
|
|
884
|
-
const path = schemaIssue.path.map(String);
|
|
885
|
-
if (issues.some((known) => covers(known.path, path))) {
|
|
886
|
-
continue;
|
|
887
|
-
}
|
|
888
|
-
if (path.length > 0 && refusesEmpty(draft, schemaIssue)) {
|
|
889
|
-
issues.push(
|
|
890
|
-
issue(
|
|
891
|
-
"null_not_allowed",
|
|
892
|
-
path,
|
|
893
|
-
"This field cannot be null. Leave it out if it is optional, or give it a value."
|
|
894
|
-
)
|
|
895
|
-
);
|
|
896
|
-
continue;
|
|
897
|
-
}
|
|
898
|
-
issues.push({
|
|
899
|
-
path,
|
|
900
|
-
message: schemaIssue.message,
|
|
901
|
-
code: schemaIssue.code,
|
|
902
|
-
severity: "invalid"
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
const stored = validateItemGroup(draft);
|
|
907
|
-
if (issues.length === 0 && stored.success) {
|
|
908
|
-
return { status: "complete", data: stored.data, issues };
|
|
909
|
-
}
|
|
910
|
-
if (issues.length === 0) {
|
|
911
|
-
for (const error of stored.success ? [] : stored.errors) {
|
|
912
|
-
issues.push({ ...error, severity: "invalid" });
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
const invalid = issues.some((found) => found.severity !== "incomplete");
|
|
916
|
-
return { status: invalid ? "invalid" : "incomplete", issues };
|
|
917
|
-
}
|
|
918
|
-
function checkTimeline(draft) {
|
|
919
|
-
const issues = [];
|
|
920
|
-
const timeline = draft.timeline;
|
|
921
|
-
if (!isRecord(timeline)) {
|
|
922
|
-
return issues;
|
|
923
|
-
}
|
|
924
|
-
const stimulus = isRecord(draft.stimulus) ? draft.stimulus : void 0;
|
|
925
|
-
if (stimulus !== void 0 && !isUnwritten(stimulus.kind) && stimulus.kind !== "video") {
|
|
926
|
-
issues.push(
|
|
927
|
-
issue(
|
|
928
|
-
"ig_timeline_stimulus_kind",
|
|
929
|
-
["stimulus", "kind"],
|
|
930
|
-
"An interactive video needs a video: quizzes open at moments of it."
|
|
931
|
-
)
|
|
932
|
-
);
|
|
933
|
-
} else if (stimulus !== void 0 && isRecord(stimulus.media) && !isUnwritten(stimulus.media.type) && stimulus.media.type !== "video") {
|
|
934
|
-
issues.push(
|
|
935
|
-
issue(
|
|
936
|
-
"ig_timeline_stimulus_kind",
|
|
937
|
-
["stimulus", "media", "type"],
|
|
938
|
-
"Upload a video file. An embedded player from another site cannot be paused when a quiz opens."
|
|
939
|
-
)
|
|
940
|
-
);
|
|
941
|
-
}
|
|
942
|
-
if (draft.shuffle === "within-group") {
|
|
943
|
-
issues.push(
|
|
944
|
-
issue(
|
|
945
|
-
"ig_timeline_shuffle",
|
|
946
|
-
["shuffle"],
|
|
947
|
-
"The questions of an interactive video appear in the order their quizzes open, so they cannot be shuffled."
|
|
948
|
-
)
|
|
949
|
-
);
|
|
950
|
-
}
|
|
951
|
-
const items = Array.isArray(draft.items) ? draft.items : [];
|
|
952
|
-
const itemIndexById = /* @__PURE__ */ new Map();
|
|
953
|
-
items.forEach((item, index) => {
|
|
954
|
-
if (!isRecord(item)) {
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
957
|
-
if (typeof item.id === "string" && item.id !== "") {
|
|
958
|
-
itemIndexById.set(item.id, index);
|
|
959
|
-
}
|
|
960
|
-
const type = item.type;
|
|
961
|
-
if (typeof type === "string" && type !== "" && type !== "item-group" && !isInteractiveVideoItemType(type) && getActivityTypeDescriptor(type) !== void 0) {
|
|
962
|
-
issues.push(
|
|
963
|
-
issue(
|
|
964
|
-
"ig_timeline_item_type",
|
|
965
|
-
["items", index, "type"],
|
|
966
|
-
`An interactive video can hold ${INTERACTIVE_VIDEO_ITEM_TYPES.join(", ")} questions only.`
|
|
967
|
-
)
|
|
968
|
-
);
|
|
969
|
-
}
|
|
970
|
-
if (type === "dictation" && isUnset(item.media)) {
|
|
971
|
-
issues.push(
|
|
972
|
-
issue(
|
|
973
|
-
"ig_timeline_dictation_media",
|
|
974
|
-
["items", index, "media"],
|
|
975
|
-
"Add the recording this dictation plays: inside a video it cannot play the video."
|
|
976
|
-
)
|
|
977
|
-
);
|
|
978
|
-
}
|
|
979
|
-
});
|
|
980
|
-
const cues = Array.isArray(timeline.cues) ? timeline.cues : [];
|
|
981
|
-
const quizIds = /* @__PURE__ */ new Set();
|
|
982
|
-
const placed = /* @__PURE__ */ new Set();
|
|
983
|
-
cues.forEach((cue, cueIndex) => {
|
|
984
|
-
if (!isRecord(cue)) {
|
|
985
|
-
return;
|
|
986
|
-
}
|
|
987
|
-
const at = (field, ...rest) => [
|
|
988
|
-
"timeline",
|
|
989
|
-
"cues",
|
|
990
|
-
cueIndex,
|
|
991
|
-
field,
|
|
992
|
-
...rest
|
|
993
|
-
];
|
|
994
|
-
if (isMissingId(cue.id)) {
|
|
995
|
-
issues.push(issue("ig_timeline_quiz_id_required", at("id"), "This quiz has no id."));
|
|
996
|
-
} else if (typeof cue.id === "string") {
|
|
997
|
-
if (quizIds.has(cue.id)) {
|
|
998
|
-
issues.push(
|
|
999
|
-
issue("ig_timeline_quiz_id_duplicate", at("id"), `Two quizzes share the id "${cue.id}".`)
|
|
1000
|
-
);
|
|
1001
|
-
}
|
|
1002
|
-
quizIds.add(cue.id);
|
|
1003
|
-
}
|
|
1004
|
-
if (isUnset(cue.at)) {
|
|
1005
|
-
issues.push(
|
|
1006
|
-
issue(
|
|
1007
|
-
"ig_timeline_quiz_time_required",
|
|
1008
|
-
at("at"),
|
|
1009
|
-
"Choose when in the video this quiz opens."
|
|
1010
|
-
)
|
|
1011
|
-
);
|
|
1012
|
-
} else if (typeof cue.at !== "number" || !Number.isFinite(cue.at) || cue.at < 0) {
|
|
1013
|
-
issues.push(
|
|
1014
|
-
issue(
|
|
1015
|
-
"ig_timeline_quiz_time_invalid",
|
|
1016
|
-
at("at"),
|
|
1017
|
-
"A quiz opens at a number of seconds, 0 or more."
|
|
1018
|
-
)
|
|
1019
|
-
);
|
|
1020
|
-
}
|
|
1021
|
-
if (isUnset(cue.itemIds) || Array.isArray(cue.itemIds) && cue.itemIds.length === 0) {
|
|
1022
|
-
issues.push(
|
|
1023
|
-
issue("ig_timeline_quiz_empty", at("itemIds"), "Add at least one question to this quiz.")
|
|
1024
|
-
);
|
|
1025
|
-
return;
|
|
1026
|
-
}
|
|
1027
|
-
if (!Array.isArray(cue.itemIds)) {
|
|
1028
|
-
return;
|
|
1029
|
-
}
|
|
1030
|
-
cue.itemIds.forEach((itemId, position) => {
|
|
1031
|
-
if (typeof itemId !== "string") {
|
|
1032
|
-
return;
|
|
1033
|
-
}
|
|
1034
|
-
if (!itemIndexById.has(itemId)) {
|
|
1035
|
-
issues.push(
|
|
1036
|
-
issue(
|
|
1037
|
-
"ig_timeline_quiz_unknown_item",
|
|
1038
|
-
at("itemIds", position),
|
|
1039
|
-
`This quiz names a question that is not in the video ("${itemId}").`
|
|
1040
|
-
)
|
|
1041
|
-
);
|
|
1042
|
-
} else if (placed.has(itemId)) {
|
|
1043
|
-
issues.push(
|
|
1044
|
-
issue(
|
|
1045
|
-
"ig_timeline_item_duplicate",
|
|
1046
|
-
at("itemIds", position),
|
|
1047
|
-
"This question is already in a quiz: each question belongs to one quiz."
|
|
1048
|
-
)
|
|
1049
|
-
);
|
|
1050
|
-
}
|
|
1051
|
-
placed.add(itemId);
|
|
1052
|
-
});
|
|
1053
|
-
});
|
|
1054
|
-
for (const [itemId, index] of itemIndexById) {
|
|
1055
|
-
if (!placed.has(itemId)) {
|
|
1056
|
-
issues.push(
|
|
1057
|
-
issue(
|
|
1058
|
-
"ig_timeline_item_unplaced",
|
|
1059
|
-
["items", index],
|
|
1060
|
-
"Add this question to a quiz, or the video never shows it."
|
|
1061
|
-
)
|
|
1062
|
-
);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
const chapters = Array.isArray(timeline.chapters) ? timeline.chapters : [];
|
|
1066
|
-
let previousAt;
|
|
1067
|
-
chapters.forEach((chapter, index) => {
|
|
1068
|
-
if (!isRecord(chapter)) {
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
1071
|
-
if (isUnwritten(chapter.title)) {
|
|
1072
|
-
issues.push(
|
|
1073
|
-
issue(
|
|
1074
|
-
"ig_timeline_chapter_title",
|
|
1075
|
-
["timeline", "chapters", index, "title"],
|
|
1076
|
-
"Give this chapter a title."
|
|
1077
|
-
)
|
|
1078
|
-
);
|
|
1079
|
-
}
|
|
1080
|
-
if (typeof chapter.at === "number" && Number.isFinite(chapter.at)) {
|
|
1081
|
-
if (previousAt !== void 0 && chapter.at <= previousAt) {
|
|
1082
|
-
issues.push(
|
|
1083
|
-
issue(
|
|
1084
|
-
"ig_timeline_chapter_order",
|
|
1085
|
-
["timeline", "chapters", index, "at"],
|
|
1086
|
-
"Chapters must start in order, each after the one before it."
|
|
1087
|
-
)
|
|
1088
|
-
);
|
|
1089
|
-
}
|
|
1090
|
-
previousAt = chapter.at;
|
|
1091
|
-
}
|
|
1092
|
-
});
|
|
1093
|
-
return issues;
|
|
1094
|
-
}
|
|
1095
|
-
function checkGroupIdentity(draft) {
|
|
1096
|
-
const issues = [];
|
|
1097
|
-
if (draft.schemaVersion !== "1.0") {
|
|
1098
|
-
issues.push(
|
|
1099
|
-
issue(
|
|
1100
|
-
"schema_version_invalid",
|
|
1101
|
-
["schemaVersion"],
|
|
1102
|
-
'The draft must have schemaVersion "1.0". A draft from createItemGroupDraft has it.'
|
|
1103
|
-
)
|
|
1104
|
-
);
|
|
1105
|
-
}
|
|
1106
|
-
if (draft.type !== "item-group") {
|
|
1107
|
-
issues.push(issue("type_mismatch", ["type"], `The draft's type must be "item-group".`));
|
|
1108
|
-
}
|
|
1109
|
-
if (isMissingId(draft.id)) {
|
|
1110
|
-
issues.push(issue("id_required", ["id"], "The group has no id."));
|
|
1111
|
-
}
|
|
1112
|
-
if (!isUnset(draft.shuffle) && draft.shuffle !== "none" && draft.shuffle !== "within-group") {
|
|
1113
|
-
issues.push(
|
|
1114
|
-
issue(
|
|
1115
|
-
"ig_shuffle_invalid",
|
|
1116
|
-
["shuffle"],
|
|
1117
|
-
'Shuffling is either "none" or "within-group". Leave it out to keep the authored order.'
|
|
1118
|
-
)
|
|
1119
|
-
);
|
|
1120
|
-
}
|
|
1121
|
-
return issues;
|
|
1122
|
-
}
|
|
1123
|
-
var KINDS = /* @__PURE__ */ new Set(["text", "audio", "video", "image", "mixed"]);
|
|
1124
|
-
var MEDIA_FOR_KIND = {
|
|
1125
|
-
audio: ["audio"],
|
|
1126
|
-
video: ["video", "embed"],
|
|
1127
|
-
image: ["image"],
|
|
1128
|
-
mixed: ["audio", "video", "image", "embed"]
|
|
1129
|
-
};
|
|
1130
|
-
function checkStimulus(stimulus) {
|
|
1131
|
-
const issues = [];
|
|
1132
|
-
const at = (...rest) => ["stimulus", ...rest];
|
|
1133
|
-
if (isMissingId(stimulus.id)) {
|
|
1134
|
-
issues.push(issue("ig_stimulus_id_required", at("id"), "The stimulus has no id."));
|
|
1135
|
-
}
|
|
1136
|
-
const kind = stimulus.kind;
|
|
1137
|
-
if (isUnwritten(kind)) {
|
|
1138
|
-
issues.push(
|
|
1139
|
-
issue(
|
|
1140
|
-
"ig_stimulus_kind_required",
|
|
1141
|
-
at("kind"),
|
|
1142
|
-
"Choose what the questions are about: a text, a recording, a video, an image, or a mix."
|
|
1143
|
-
)
|
|
1144
|
-
);
|
|
1145
|
-
} else if (typeof kind !== "string" || !KINDS.has(kind)) {
|
|
1146
|
-
if (typeof kind === "string") {
|
|
1147
|
-
issues.push(
|
|
1148
|
-
issue(
|
|
1149
|
-
"ig_stimulus_kind_invalid",
|
|
1150
|
-
at("kind"),
|
|
1151
|
-
`"${kind}" is not a stimulus kind. Use text, audio, video, image or mixed.`
|
|
1152
|
-
)
|
|
1153
|
-
);
|
|
1154
|
-
}
|
|
1155
|
-
if (isRecord(stimulus.media)) {
|
|
1156
|
-
issues.push(...checkMedia(stimulus.media, at("media")));
|
|
1157
|
-
}
|
|
1158
|
-
return issues;
|
|
1159
|
-
}
|
|
1160
|
-
const hasBody = typeof stimulus.body === "string" && stimulus.body.trim() !== "";
|
|
1161
|
-
const htmlPresent = !isUnset(stimulus.bodyHtml);
|
|
1162
|
-
if (!hasBody && (kind === "text" || kind === "mixed" || htmlPresent)) {
|
|
1163
|
-
issues.push(
|
|
1164
|
-
issue(
|
|
1165
|
-
"ig_stimulus_body_required",
|
|
1166
|
-
at("body"),
|
|
1167
|
-
isUnwritten(stimulus.bodyHtml) ? "Write the passage." : "Write the passage as plain text too. The rich-text version is shown only where a sanitiser is supplied."
|
|
1168
|
-
)
|
|
1169
|
-
);
|
|
1170
|
-
}
|
|
1171
|
-
if (kind !== "text" && isUnset(stimulus.media)) {
|
|
1172
|
-
issues.push(
|
|
1173
|
-
issue("ig_stimulus_media_required", at("media"), "Add the recording, video or image.")
|
|
1174
|
-
);
|
|
1175
|
-
} else if (isRecord(stimulus.media)) {
|
|
1176
|
-
const allowed = MEDIA_FOR_KIND[kind];
|
|
1177
|
-
const mediaType = stimulus.media.type;
|
|
1178
|
-
if (allowed !== void 0 && typeof mediaType === "string" && !allowed.includes(mediaType)) {
|
|
1179
|
-
issues.push(
|
|
1180
|
-
issue(
|
|
1181
|
-
"ig_stimulus_media_kind",
|
|
1182
|
-
at("media", "type"),
|
|
1183
|
-
`A "${kind}" stimulus cannot carry ${mediaType} media. Use ${allowed.join(" or ")}.`
|
|
1184
|
-
)
|
|
1185
|
-
);
|
|
1186
|
-
}
|
|
1187
|
-
issues.push(...checkMedia(stimulus.media, at("media")));
|
|
1188
|
-
}
|
|
1189
|
-
return issues;
|
|
1190
|
-
}
|
|
1191
|
-
function checkItems(draft) {
|
|
1192
|
-
const issues = [];
|
|
1193
|
-
const items = isUnset(draft.items) ? [] : draft.items;
|
|
1194
|
-
if (!Array.isArray(items)) {
|
|
1195
|
-
return issues;
|
|
1196
|
-
}
|
|
1197
|
-
if (items.length === 0) {
|
|
1198
|
-
issues.push(
|
|
1199
|
-
issue("ig_items_required", ["items"], "Add at least one question about this material.")
|
|
1200
|
-
);
|
|
1201
|
-
return issues;
|
|
1202
|
-
}
|
|
1203
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1204
|
-
const repeated = /* @__PURE__ */ new Set();
|
|
1205
|
-
for (const [index, item] of items.entries()) {
|
|
1206
|
-
if (!isRecord(item)) {
|
|
1207
|
-
continue;
|
|
1208
|
-
}
|
|
1209
|
-
const ordinal = index + 1;
|
|
1210
|
-
if (isMissingId(item.id)) {
|
|
1211
|
-
issues.push(
|
|
1212
|
-
issue("ig_item_id_required", ["items", index, "id"], `Question ${ordinal} has no id.`)
|
|
1213
|
-
);
|
|
1214
|
-
} else if (typeof item.id === "string") {
|
|
1215
|
-
if (seen.has(item.id)) {
|
|
1216
|
-
repeated.add(item.id);
|
|
1217
|
-
}
|
|
1218
|
-
seen.add(item.id);
|
|
1219
|
-
}
|
|
1220
|
-
if (item.type === "item-group") {
|
|
1221
|
-
issues.push(
|
|
1222
|
-
issue(
|
|
1223
|
-
"ig_item_nested_group",
|
|
1224
|
-
["items", index, "type"],
|
|
1225
|
-
"Groups do not nest: every item must be an activity."
|
|
1226
|
-
)
|
|
1227
|
-
);
|
|
1228
|
-
continue;
|
|
1229
|
-
}
|
|
1230
|
-
if (isUnwritten(item.type)) {
|
|
1231
|
-
issues.push(
|
|
1232
|
-
issue(
|
|
1233
|
-
"ig_item_type_required",
|
|
1234
|
-
["items", index, "type"],
|
|
1235
|
-
`Choose what kind of question ${ordinal} is.`
|
|
1236
|
-
)
|
|
1237
|
-
);
|
|
1238
|
-
continue;
|
|
1239
|
-
}
|
|
1240
|
-
if (typeof item.type !== "string") {
|
|
1241
|
-
continue;
|
|
1242
|
-
}
|
|
1243
|
-
let result;
|
|
1244
|
-
try {
|
|
1245
|
-
result = validateDraft(item.type, item);
|
|
1246
|
-
} catch (error) {
|
|
1247
|
-
if (!(error instanceof UnknownActivityTypeError) || error.activityType !== item.type) {
|
|
1248
|
-
throw error;
|
|
1249
|
-
}
|
|
1250
|
-
issues.push(
|
|
1251
|
-
issue(
|
|
1252
|
-
"ig_item_type_unknown",
|
|
1253
|
-
["items", index, "type"],
|
|
1254
|
-
`"${item.type}" is not a registered activity type.`
|
|
1255
|
-
)
|
|
1256
|
-
);
|
|
1257
|
-
continue;
|
|
1258
|
-
}
|
|
1259
|
-
for (const found of result.issues) {
|
|
1260
|
-
issues.push({ ...found, path: ["items", String(index), ...found.path] });
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
for (const id of repeated) {
|
|
1264
|
-
issues.push(
|
|
1265
|
-
issue("ig_item_id_duplicate", ["items"], `More than one question has the id "${id}".`)
|
|
1266
|
-
);
|
|
1267
|
-
}
|
|
1268
|
-
return issues;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
// src/authoring/index.ts
|
|
1272
|
-
function validateDraft(type, draft) {
|
|
1273
|
-
return withValidationScope(() => validateDraftInScope(type, draft));
|
|
1274
|
-
}
|
|
1275
|
-
function validateDraftInScope(type, draft) {
|
|
1276
|
-
const descriptor = getActivityTypeDescriptor(type);
|
|
1277
|
-
if (descriptor === void 0) {
|
|
1278
|
-
throw new UnknownActivityTypeError(String(type));
|
|
1279
|
-
}
|
|
1280
|
-
const reported = isRecord(draft) && descriptor.authoring?.checkDraft !== void 0 ? descriptor.authoring.checkDraft(draft).map(normalise) : [];
|
|
1281
|
-
const issues = [...reported];
|
|
1282
|
-
const parsed = readSchema(descriptor.schema, draft, `Activity type "${descriptor.type}"`, {
|
|
1283
|
-
reportInput: true
|
|
1284
|
-
});
|
|
1285
|
-
if (!parsed.success) {
|
|
1286
|
-
const covered = coveredPathsOf(reported);
|
|
1287
|
-
const reportedEmpty = /* @__PURE__ */ new Set();
|
|
1288
|
-
for (const schemaIssue of parsed.issues) {
|
|
1289
|
-
const path = schemaIssue.path.map(String);
|
|
1290
|
-
if (covered.has(pathKey(path))) {
|
|
1291
|
-
continue;
|
|
1292
|
-
}
|
|
1293
|
-
if (path.length > 0 && refusesEmpty(draft, schemaIssue)) {
|
|
1294
|
-
const key = JSON.stringify(path);
|
|
1295
|
-
if (!reportedEmpty.has(key)) {
|
|
1296
|
-
reportedEmpty.add(key);
|
|
1297
|
-
issues.push(
|
|
1298
|
-
issue(
|
|
1299
|
-
"null_not_allowed",
|
|
1300
|
-
path,
|
|
1301
|
-
typeof schemaIssue.path.at(-1) === "number" ? "This entry has no value. Remove it, or fill it in." : "This field cannot be null. Leave it out if it is optional, or give it a value."
|
|
1302
|
-
)
|
|
1303
|
-
);
|
|
1304
|
-
}
|
|
1305
|
-
continue;
|
|
1306
|
-
}
|
|
1307
|
-
issues.push({
|
|
1308
|
-
path,
|
|
1309
|
-
message: schemaIssue.message,
|
|
1310
|
-
code: schemaIssue.code,
|
|
1311
|
-
severity: "invalid"
|
|
1312
|
-
});
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
if (parsed.success && issues.length === 0) {
|
|
1316
|
-
return { status: "complete", data: parsed.data, issues };
|
|
1317
|
-
}
|
|
1318
|
-
const invalid = issues.some((found) => found.severity !== "incomplete");
|
|
1319
|
-
return { status: invalid ? "invalid" : "incomplete", issues };
|
|
1320
|
-
}
|
|
1321
|
-
function createDraft(type, context) {
|
|
1322
|
-
const descriptor = getActivityTypeDescriptor(type);
|
|
1323
|
-
if (descriptor === void 0) {
|
|
1324
|
-
throw new UnknownActivityTypeError(String(type));
|
|
1325
|
-
}
|
|
1326
|
-
const create = descriptor.authoring?.createDraft;
|
|
1327
|
-
if (create === void 0) {
|
|
1328
|
-
throw new Error(
|
|
1329
|
-
`Activity type "${String(type)}" declares no authoring.createDraft, so there is no empty draft to create. Build the draft yourself; validateDraft works for every registered type.`
|
|
1330
|
-
);
|
|
1331
|
-
}
|
|
1332
|
-
const issued = /* @__PURE__ */ new Set();
|
|
1333
|
-
const newId = () => {
|
|
1334
|
-
const id = context.newId();
|
|
1335
|
-
if (typeof id !== "string" || id === "") {
|
|
1336
|
-
throw new Error("createDraft: newId() must return a non-empty string.");
|
|
1337
|
-
}
|
|
1338
|
-
if (issued.has(id)) {
|
|
1339
|
-
throw new Error(
|
|
1340
|
-
`createDraft: newId() returned "${id}" twice. Ids within one draft must differ \u2014 two options sharing an id cannot be scored apart.`
|
|
1341
|
-
);
|
|
1342
|
-
}
|
|
1343
|
-
issued.add(id);
|
|
1344
|
-
return id;
|
|
1345
|
-
};
|
|
1346
|
-
return create({ newId });
|
|
1347
|
-
}
|
|
1348
|
-
function normalise(found) {
|
|
1349
|
-
const documented = Object.hasOwn(DRAFT_ISSUE_SEVERITY, found.code) ? DRAFT_ISSUE_SEVERITY[found.code] : void 0;
|
|
1350
|
-
return {
|
|
1351
|
-
...found,
|
|
1352
|
-
path: found.path.map(String),
|
|
1353
|
-
severity: documented ?? (found.severity === "incomplete" ? "incomplete" : "invalid")
|
|
1354
|
-
};
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
822
|
// src/redact.ts
|
|
1358
823
|
function mergePolicy(base, overrides) {
|
|
1359
824
|
if (overrides === void 0) {
|
|
@@ -1431,10 +896,10 @@ function redact(data, options = {}) {
|
|
|
1431
896
|
if (!parsed.success) {
|
|
1432
897
|
throw new ActivitySchemaError(
|
|
1433
898
|
descriptor.type,
|
|
1434
|
-
parsed.issues.map((
|
|
1435
|
-
path:
|
|
1436
|
-
message:
|
|
1437
|
-
code:
|
|
899
|
+
parsed.issues.map((issue) => ({
|
|
900
|
+
path: issue.path.map(String),
|
|
901
|
+
message: issue.message,
|
|
902
|
+
code: issue.code
|
|
1438
903
|
}))
|
|
1439
904
|
);
|
|
1440
905
|
}
|
|
@@ -1479,10 +944,10 @@ function assertRedacted(data) {
|
|
|
1479
944
|
if (!parsed.success) {
|
|
1480
945
|
throw new ActivitySchemaError(
|
|
1481
946
|
descriptor.type,
|
|
1482
|
-
parsed.issues.map((
|
|
1483
|
-
path:
|
|
1484
|
-
message:
|
|
1485
|
-
code:
|
|
947
|
+
parsed.issues.map((issue) => ({
|
|
948
|
+
path: issue.path.map(String),
|
|
949
|
+
message: issue.message,
|
|
950
|
+
code: issue.code
|
|
1486
951
|
}))
|
|
1487
952
|
);
|
|
1488
953
|
}
|
|
@@ -1541,10 +1006,10 @@ function redactItemGroup(group, options = {}) {
|
|
|
1541
1006
|
if (!parsed.success) {
|
|
1542
1007
|
throw new ActivitySchemaError(
|
|
1543
1008
|
"item-group",
|
|
1544
|
-
parsed.error.issues.map((
|
|
1545
|
-
path:
|
|
1546
|
-
message:
|
|
1547
|
-
code:
|
|
1009
|
+
parsed.error.issues.map((issue) => ({
|
|
1010
|
+
path: issue.path.map(String),
|
|
1011
|
+
message: issue.message,
|
|
1012
|
+
code: issue.code
|
|
1548
1013
|
}))
|
|
1549
1014
|
);
|
|
1550
1015
|
}
|
|
@@ -1571,10 +1036,10 @@ function assertRedactedItemGroup(data) {
|
|
|
1571
1036
|
if (!parsed.success) {
|
|
1572
1037
|
throw new ActivitySchemaError(
|
|
1573
1038
|
"item-group",
|
|
1574
|
-
parsed.error.issues.map((
|
|
1575
|
-
path:
|
|
1576
|
-
message:
|
|
1577
|
-
code:
|
|
1039
|
+
parsed.error.issues.map((issue) => ({
|
|
1040
|
+
path: issue.path.map(String),
|
|
1041
|
+
message: issue.message,
|
|
1042
|
+
code: issue.code
|
|
1578
1043
|
}))
|
|
1579
1044
|
);
|
|
1580
1045
|
}
|
|
@@ -1585,9 +1050,9 @@ function assertRedactedItemGroup(data) {
|
|
|
1585
1050
|
if (error instanceof ActivitySchemaError) {
|
|
1586
1051
|
throw new ActivitySchemaError(
|
|
1587
1052
|
"item-group",
|
|
1588
|
-
error.errors.map((
|
|
1589
|
-
...
|
|
1590
|
-
path: ["items", String(index), ...
|
|
1053
|
+
error.errors.map((issue) => ({
|
|
1054
|
+
...issue,
|
|
1055
|
+
path: ["items", String(index), ...issue.path]
|
|
1591
1056
|
}))
|
|
1592
1057
|
);
|
|
1593
1058
|
}
|
|
@@ -1631,6 +1096,12 @@ function composeTimelineScore(entryKey, items, policy) {
|
|
|
1631
1096
|
return composeAssessmentScore([{ id: entryKey, weight: 1, items: own }], policy);
|
|
1632
1097
|
}
|
|
1633
1098
|
export {
|
|
1099
|
+
AI_COACHING_MAX_TIP_LENGTH,
|
|
1100
|
+
AI_COACHING_MAX_WORDS,
|
|
1101
|
+
AI_CRITIQUE_MAX_FIELD_LENGTH,
|
|
1102
|
+
AI_CRITIQUE_MAX_FINDINGS,
|
|
1103
|
+
AI_DRAFTS_MAX_COUNT,
|
|
1104
|
+
AI_DRAFTS_MAX_REPAIRS,
|
|
1634
1105
|
AI_EXPLANATION_TYPES,
|
|
1635
1106
|
AI_HINT_TYPES,
|
|
1636
1107
|
AI_TEXT_MAX_LENGTH,
|
|
@@ -1662,6 +1133,10 @@ export {
|
|
|
1662
1133
|
XAPIVerb,
|
|
1663
1134
|
XAPI_VERB_DISPLAY,
|
|
1664
1135
|
aiAllowedByContent,
|
|
1136
|
+
aiCoachingRequest,
|
|
1137
|
+
aiCritiqueRequest,
|
|
1138
|
+
aiDraftsRepairRequest,
|
|
1139
|
+
aiDraftsRequest,
|
|
1665
1140
|
aiExplanationRequest,
|
|
1666
1141
|
aiGradeOf,
|
|
1667
1142
|
aiHintRequest,
|
|
@@ -1673,6 +1148,9 @@ export {
|
|
|
1673
1148
|
assertRedactedItemGroup,
|
|
1674
1149
|
buildAiFacts,
|
|
1675
1150
|
canonicalJson,
|
|
1151
|
+
checkAiCoaching,
|
|
1152
|
+
checkAiCritique,
|
|
1153
|
+
checkAiDrafts,
|
|
1676
1154
|
checkAiExplanation,
|
|
1677
1155
|
checkAiHint,
|
|
1678
1156
|
checkAiWritingFeedback,
|
|
@@ -1686,6 +1164,9 @@ export {
|
|
|
1686
1164
|
createDraft,
|
|
1687
1165
|
createInteractiveVideoDraft,
|
|
1688
1166
|
createItemGroupDraft,
|
|
1167
|
+
critiqueDraft,
|
|
1168
|
+
critiqueDrafts,
|
|
1169
|
+
critiqueItemGroupDraft,
|
|
1689
1170
|
defineActivityType,
|
|
1690
1171
|
dictationJsonSchema,
|
|
1691
1172
|
dictationReferenceWords,
|
|
@@ -1701,6 +1182,7 @@ export {
|
|
|
1701
1182
|
flattenSequence,
|
|
1702
1183
|
gapSelectJsonSchema,
|
|
1703
1184
|
gapSelectType,
|
|
1185
|
+
generateDrafts,
|
|
1704
1186
|
getActivityTypeDescriptor,
|
|
1705
1187
|
gradeFromRubric,
|
|
1706
1188
|
gradeReadAloud,
|
|
@@ -1709,6 +1191,7 @@ export {
|
|
|
1709
1191
|
hashSeed,
|
|
1710
1192
|
hintRevealsAnswer,
|
|
1711
1193
|
inspectWav,
|
|
1194
|
+
interactiveVideoFromDrafts,
|
|
1712
1195
|
isInteractiveVideoItemType,
|
|
1713
1196
|
isItemGroup,
|
|
1714
1197
|
itemGroupJsonSchema,
|