@intellectif/lk-core 1.5.0 → 1.7.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 +77 -0
- package/dist/{ai-BYryFkF9.d.ts → ai-B1wYrqcB.d.cts} +83 -18
- package/dist/ai-B1wYrqcB.d.cts.map +1 -0
- package/dist/{ai-BYryFkF9.d.cts → ai-B1wYrqcB.d.ts} +83 -18
- package/dist/ai-B1wYrqcB.d.ts.map +1 -0
- package/dist/ai-check.cjs +13 -6
- package/dist/ai-check.cjs.map +1 -1
- package/dist/ai-check.d.cts +1 -5
- package/dist/ai-check.d.cts.map +1 -1
- package/dist/ai-check.d.ts +1 -5
- package/dist/ai-check.d.ts.map +1 -1
- package/dist/ai-check.js +13 -6
- package/dist/ai-check.js.map +1 -1
- package/dist/{ai-writing-D8PdhJJz.js → ai-writing-B7oMCt66.js} +311 -66
- package/dist/ai-writing-B7oMCt66.js.map +1 -0
- package/dist/{ai-writing-Cen7-3qs.cjs → ai-writing-DrPTV3R2.cjs} +313 -50
- package/dist/ai-writing-DrPTV3R2.cjs.map +1 -0
- package/dist/{index-DUaW1Tj5.d.ts → index-BM8LeDWn.d.cts} +2 -42
- package/dist/index-BM8LeDWn.d.cts.map +1 -0
- package/dist/{index-Bg0aeWtM.d.cts → index-BTkMQtws.d.ts} +2 -42
- package/dist/index-BTkMQtws.d.ts.map +1 -0
- package/dist/{index-wMc1E_aM.d.ts → index-BU99GFTl.d.ts} +2 -8
- package/dist/index-BU99GFTl.d.ts.map +1 -0
- package/dist/{index-CxpiWedW.d.ts → index-BdXs4-en.d.ts} +42 -20
- package/dist/index-BdXs4-en.d.ts.map +1 -0
- package/dist/{index-qMbxed1B.d.cts → index-DP9rvyOz.d.cts} +42 -20
- package/dist/index-DP9rvyOz.d.cts.map +1 -0
- package/dist/{index-D7R3Ll8R.d.cts → index-XCCoPoTU.d.cts} +2 -8
- package/dist/index-XCCoPoTU.d.cts.map +1 -0
- package/dist/index.cjs +70 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +135 -69
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +135 -69
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +65 -22
- package/dist/index.js.map +1 -1
- package/dist/{item-group-DEB7nLID.cjs → item-group-8P3WFuUK.cjs} +5 -6
- package/dist/item-group-8P3WFuUK.cjs.map +1 -0
- package/dist/{item-group-DdSUCUjM.js → item-group-CeDk7mfM.js} +5 -6
- package/dist/item-group-CeDk7mfM.js.map +1 -0
- package/dist/{registry-DaZRpqjW.js → registry-D92hz9iA.js} +124 -106
- package/dist/registry-D92hz9iA.js.map +1 -0
- package/dist/{registry-Ce00A_OW.cjs → registry-hzrkTeIT.cjs} +142 -107
- package/dist/registry-hzrkTeIT.cjs.map +1 -0
- package/dist/{schemas-DksueYsu.cjs → schemas-BWeskONy.cjs} +79 -18
- package/dist/schemas-BWeskONy.cjs.map +1 -0
- package/dist/{schemas-BioKFf3F.js → schemas-C7TViWCg.js} +74 -19
- package/dist/schemas-C7TViWCg.js.map +1 -0
- package/dist/schemas.cjs +5 -2
- package/dist/schemas.d.cts +2 -2
- package/dist/schemas.d.ts +2 -2
- package/dist/schemas.js +4 -3
- package/dist/{scoring-lKQVYsxy.cjs → scoring-CtkCFyFT.cjs} +22 -42
- package/dist/{scoring-lKQVYsxy.cjs.map → scoring-CtkCFyFT.cjs.map} +1 -1
- package/dist/{scoring-BKNNTz-s.js → scoring-DD1GHcLI.js} +22 -42
- package/dist/{scoring-BKNNTz-s.js.map → scoring-DD1GHcLI.js.map} +1 -1
- package/dist/scoring.cjs +2 -2
- package/dist/scoring.d.cts +2 -2
- package/dist/scoring.d.ts +2 -2
- package/dist/scoring.js +2 -2
- package/dist/{xapi-NYrTlpID.cjs → xapi-7btSpx6v.cjs} +5 -8
- package/dist/{xapi-NYrTlpID.cjs.map → xapi-7btSpx6v.cjs.map} +1 -1
- package/dist/{xapi-wR6gu4qS.js → xapi-Cgicg-zU.js} +5 -8
- package/dist/{xapi-wR6gu4qS.js.map → xapi-Cgicg-zU.js.map} +1 -1
- package/dist/xapi.cjs +1 -1
- package/dist/xapi.d.cts +1 -1
- package/dist/xapi.d.ts +1 -1
- package/dist/xapi.js +1 -1
- package/package.json +2 -2
- package/dist/ai-BYryFkF9.d.cts.map +0 -1
- package/dist/ai-BYryFkF9.d.ts.map +0 -1
- package/dist/ai-writing-Cen7-3qs.cjs.map +0 -1
- package/dist/ai-writing-D8PdhJJz.js.map +0 -1
- package/dist/index-Bg0aeWtM.d.cts.map +0 -1
- package/dist/index-CxpiWedW.d.ts.map +0 -1
- package/dist/index-D7R3Ll8R.d.cts.map +0 -1
- package/dist/index-DUaW1Tj5.d.ts.map +0 -1
- package/dist/index-qMbxed1B.d.cts.map +0 -1
- package/dist/index-wMc1E_aM.d.ts.map +0 -1
- package/dist/item-group-DEB7nLID.cjs.map +0 -1
- package/dist/item-group-DdSUCUjM.js.map +0 -1
- package/dist/registry-Ce00A_OW.cjs.map +0 -1
- package/dist/registry-DaZRpqjW.js.map +0 -1
- package/dist/schemas-BioKFf3F.js.map +0 -1
- package/dist/schemas-DksueYsu.cjs.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { B as
|
|
2
|
-
import { F as isGradeInRange, _ as gradeFromRubric, c as validateSpeechAssessment, r as score, s as alignReadAloud } from "./scoring-
|
|
3
|
-
import { a as INTERACTIVE_VIDEO_ITEM_TYPES, i as validateItemGroup, t as ItemGroupSchema, u as isInteractiveVideoItemType } from "./item-group-
|
|
4
|
-
|
|
1
|
+
import { A as normaliseFinding, B as coveredPathsOf, F as levelKey, G as isUnwritten, H as isMissingId, I as readRubricLevels, J as refusesEmpty, K as issue, L as countWords, N as LEVEL_PROBABILITY_TOLERANCE, O as keyPositionFinding, P as levelDistribution, R as DRAFT_ISSUE_SEVERITY, U as isRecord$6, V as covers, W as isUnset, X as GROUP_CAPTIONS_REVEAL_DICTATION, Z as groupCaptionsField, _t as containsWords, a as readSchema, bt as placeholderOrder, k as finding, pt as withValidationScope, q as pathKey, rt as alignDictation, t as getActivityTypeDescriptor, tt as UnknownActivityTypeError, vt as fold, xt as revealsAnswer, yt as numberedPassage, z as checkMedia } from "./registry-D92hz9iA.js";
|
|
2
|
+
import { F as isGradeInRange, _ as gradeFromRubric, c as validateSpeechAssessment, r as score, s as alignReadAloud } from "./scoring-DD1GHcLI.js";
|
|
3
|
+
import { a as INTERACTIVE_VIDEO_ITEM_TYPES, i as validateItemGroup, t as ItemGroupSchema, u as isInteractiveVideoItemType } from "./item-group-CeDk7mfM.js";
|
|
4
|
+
// src/ai-material.ts
|
|
5
5
|
/**
|
|
6
6
|
* What a model is told about the group a question sits in: the material the
|
|
7
7
|
* learner reads and sees beside it.
|
|
@@ -28,7 +28,7 @@ const MEDIA_TYPES = {
|
|
|
28
28
|
video: true,
|
|
29
29
|
embed: true
|
|
30
30
|
};
|
|
31
|
-
const isRecord$
|
|
31
|
+
const isRecord$5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
32
32
|
/** A string with something in it, as written; otherwise nothing. */
|
|
33
33
|
const filled = (value) => typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
34
34
|
/**
|
|
@@ -62,17 +62,17 @@ function cut(text) {
|
|
|
62
62
|
* and a signed address must not end up in a prompt. Each field is read once.
|
|
63
63
|
*/
|
|
64
64
|
function materialFacts(group) {
|
|
65
|
-
if (!isRecord$
|
|
65
|
+
if (!isRecord$5(group)) return null;
|
|
66
66
|
const stimulus = group.stimulus;
|
|
67
|
-
if (!isRecord$
|
|
67
|
+
if (!isRecord$5(stimulus)) return null;
|
|
68
68
|
const kind = stimulus.kind;
|
|
69
69
|
if (typeof kind !== "string" || !Object.hasOwn(KINDS$2, kind)) return null;
|
|
70
70
|
const title = filled(stimulus.title) ?? filled(group.title);
|
|
71
71
|
const body = filled(stimulus.body);
|
|
72
72
|
const text = body === void 0 ? void 0 : cut(body);
|
|
73
73
|
const rawMedia = stimulus.media;
|
|
74
|
-
const mediaType = isRecord$
|
|
75
|
-
const alt = isRecord$
|
|
74
|
+
const mediaType = isRecord$5(rawMedia) ? rawMedia.type : void 0;
|
|
75
|
+
const alt = isRecord$5(rawMedia) ? filled(rawMedia.alt) : void 0;
|
|
76
76
|
const media = typeof mediaType === "string" && Object.hasOwn(MEDIA_TYPES, mediaType) ? {
|
|
77
77
|
type: mediaType,
|
|
78
78
|
...alt !== void 0 ? { alt } : {}
|
|
@@ -89,8 +89,7 @@ function materialFacts(group) {
|
|
|
89
89
|
...locale !== void 0 ? { locale } : {}
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
//#region src/ai-text.ts
|
|
92
|
+
// src/ai-text.ts
|
|
94
93
|
/**
|
|
95
94
|
* How the SDK reads text a model wrote, before a learner does. Internal: shared
|
|
96
95
|
* by the AI checks, and exported by none of the package's entry points.
|
|
@@ -178,8 +177,7 @@ function folded(text) {
|
|
|
178
177
|
}
|
|
179
178
|
/** A quote as it is searched for: folded, and without the spaces around it. */
|
|
180
179
|
const needle = (quote) => folded(quote).chars.trim();
|
|
181
|
-
|
|
182
|
-
//#region src/ai.ts
|
|
180
|
+
// src/ai.ts
|
|
183
181
|
/**
|
|
184
182
|
* The activity types the SDK builds facts for, and which of them take hints.
|
|
185
183
|
* A dictation takes none: its answer is the whole sentence, and it has
|
|
@@ -284,6 +282,11 @@ const base = (data) => ({
|
|
|
284
282
|
title: data.title,
|
|
285
283
|
...typeof data.locale === "string" ? { locale: data.locale } : {}
|
|
286
284
|
});
|
|
285
|
+
/** The item's instructions, trimmed; nothing for none, or for blank ones the learner never sees. */
|
|
286
|
+
function instructionsOf$1(data) {
|
|
287
|
+
const text = data.instructions;
|
|
288
|
+
return typeof text === "string" && text.trim() !== "" ? { instructions: text.trim() } : {};
|
|
289
|
+
}
|
|
287
290
|
/**
|
|
288
291
|
* The facts a model is given about one item and one answer. `details` are the
|
|
289
292
|
* SDK's per-part verdicts — from its own scorer, or from a server's outcome in
|
|
@@ -303,6 +306,7 @@ function buildAiFacts(data, response, details) {
|
|
|
303
306
|
return {
|
|
304
307
|
...base(data),
|
|
305
308
|
activityType: "multiple-choice",
|
|
309
|
+
...instructionsOf$1(data),
|
|
306
310
|
question: item.question,
|
|
307
311
|
mode: item.mode,
|
|
308
312
|
options: item.options.map((option) => {
|
|
@@ -328,6 +332,7 @@ function buildAiFacts(data, response, details) {
|
|
|
328
332
|
return {
|
|
329
333
|
...base(data),
|
|
330
334
|
activityType: "fill-in-the-blanks",
|
|
335
|
+
...instructionsOf$1(data),
|
|
331
336
|
passage: numberedPassage(item.passage),
|
|
332
337
|
blanks: placeholderOrder(item.passage).map((id, index) => {
|
|
333
338
|
const blank = blanks.get(id);
|
|
@@ -353,6 +358,7 @@ function buildAiFacts(data, response, details) {
|
|
|
353
358
|
return {
|
|
354
359
|
...base(data),
|
|
355
360
|
activityType: "gap-select",
|
|
361
|
+
...instructionsOf$1(data),
|
|
356
362
|
passage: numberedPassage(item.passage),
|
|
357
363
|
gaps: placeholderOrder(item.passage).map((id, index) => {
|
|
358
364
|
const gap = gaps.get(id);
|
|
@@ -596,8 +602,7 @@ function hintRevealsAnswer(facts, hint) {
|
|
|
596
602
|
default: return true;
|
|
597
603
|
}
|
|
598
604
|
}
|
|
599
|
-
|
|
600
|
-
//#region src/ai-coaching.ts
|
|
605
|
+
// src/ai-coaching.ts
|
|
601
606
|
/** The most words one coaching may work on. More is refused, not cut. */
|
|
602
607
|
const AI_COACHING_MAX_WORDS = 20;
|
|
603
608
|
/** The longest tip for one word, in code points. */
|
|
@@ -742,7 +747,7 @@ function aiCoachingRequest(input) {
|
|
|
742
747
|
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
743
748
|
};
|
|
744
749
|
}
|
|
745
|
-
const isRecord$
|
|
750
|
+
const isRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
746
751
|
/** A symbol as it is compared: composed, and without the spaces around it. */
|
|
747
752
|
const symbolKey = (symbol) => symbol.normalize("NFC").trim();
|
|
748
753
|
/**
|
|
@@ -791,7 +796,7 @@ function checkAiCoaching(raw, request) {
|
|
|
791
796
|
});
|
|
792
797
|
const seen = /* @__PURE__ */ new Set();
|
|
793
798
|
for (const entry of entries) {
|
|
794
|
-
if (!isRecord$
|
|
799
|
+
if (!isRecord$4(entry) || typeof entry.itemId !== "string" || typeof entry.tip !== "string") return {
|
|
795
800
|
ok: false,
|
|
796
801
|
refusal: "malformed"
|
|
797
802
|
};
|
|
@@ -839,7 +844,7 @@ function checkAiCoaching(raw, request) {
|
|
|
839
844
|
/** A sound a model names, checked against the sounds the engine reported for the word. */
|
|
840
845
|
function checkSound(raw, fact) {
|
|
841
846
|
if (raw === void 0 || raw === null) return;
|
|
842
|
-
if (!isRecord$
|
|
847
|
+
if (!isRecord$4(raw) || typeof raw.expected !== "string" || raw.heard !== void 0 && raw.heard !== null && typeof raw.heard !== "string") return {
|
|
843
848
|
ok: false,
|
|
844
849
|
refusal: "malformed"
|
|
845
850
|
};
|
|
@@ -865,8 +870,239 @@ function checkSound(raw, fact) {
|
|
|
865
870
|
heard: instead.symbol
|
|
866
871
|
};
|
|
867
872
|
}
|
|
868
|
-
|
|
869
|
-
|
|
873
|
+
// src/media-refs.ts
|
|
874
|
+
const MAIN_KINDS = [
|
|
875
|
+
"audio",
|
|
876
|
+
"video",
|
|
877
|
+
"image",
|
|
878
|
+
"embed"
|
|
879
|
+
];
|
|
880
|
+
function isRecord$3(value) {
|
|
881
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
882
|
+
}
|
|
883
|
+
function valueAt(root, path) {
|
|
884
|
+
let at = root;
|
|
885
|
+
for (const key of path) {
|
|
886
|
+
if (typeof key === "number" ? !Array.isArray(at) : !isRecord$3(at)) return;
|
|
887
|
+
at = at[key];
|
|
888
|
+
}
|
|
889
|
+
return at;
|
|
890
|
+
}
|
|
891
|
+
/** Where `item` keeps its files: what its type declares, or its `media` alone. */
|
|
892
|
+
function slotsOf(item) {
|
|
893
|
+
const declared = typeof item.type === "string" ? getActivityTypeDescriptor(item.type)?.mediaSlots : void 0;
|
|
894
|
+
if (declared === void 0) return [{ path: ["media"] }];
|
|
895
|
+
const slots = declared(item);
|
|
896
|
+
return Array.isArray(slots) ? slots : [];
|
|
897
|
+
}
|
|
898
|
+
function holderRefs(holder, path, kindOfSlot, owner) {
|
|
899
|
+
if (!isRecord$3(holder)) return [];
|
|
900
|
+
const main = typeof holder.type === "string" && MAIN_KINDS.includes(holder.type) ? holder.type : kindOfSlot;
|
|
901
|
+
const refs = [];
|
|
902
|
+
const add = (field, kind, url) => {
|
|
903
|
+
refs.push({
|
|
904
|
+
path: [...path, ...field],
|
|
905
|
+
kind,
|
|
906
|
+
url,
|
|
907
|
+
holder,
|
|
908
|
+
...owner
|
|
909
|
+
});
|
|
910
|
+
};
|
|
911
|
+
if (main !== void 0) add(["url"], main, typeof holder.url === "string" ? holder.url : null);
|
|
912
|
+
if (typeof holder.captionsUrl === "string") add(["captionsUrl"], "captions", holder.captionsUrl);
|
|
913
|
+
if (Array.isArray(holder.tracks)) holder.tracks.forEach((track, index) => {
|
|
914
|
+
if (isRecord$3(track) && typeof track.src === "string") refs.push({
|
|
915
|
+
path: [
|
|
916
|
+
...path,
|
|
917
|
+
"tracks",
|
|
918
|
+
index,
|
|
919
|
+
"src"
|
|
920
|
+
],
|
|
921
|
+
kind: "track",
|
|
922
|
+
url: track.src,
|
|
923
|
+
holder: track,
|
|
924
|
+
...owner,
|
|
925
|
+
...typeof track.srclang === "string" ? { srclang: track.srclang } : {}
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
if (typeof holder.poster === "string") add(["poster"], "poster", holder.poster);
|
|
929
|
+
return refs;
|
|
930
|
+
}
|
|
931
|
+
function activityRefs(item, base) {
|
|
932
|
+
if (!isRecord$3(item)) return [];
|
|
933
|
+
const owner = typeof item.id === "string" ? { activityId: item.id } : {};
|
|
934
|
+
return slotsOf(item).flatMap((slot) => holderRefs(valueAt(item, slot.path), [...base, ...slot.path], slot.kind, owner));
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Every media address in one piece of content: a loose activity or an item
|
|
938
|
+
* group (its stimulus first, then each item in authored order), as a
|
|
939
|
+
* `SequenceEntry` holds them. Drafts are read too.
|
|
940
|
+
*
|
|
941
|
+
* For each activity the walker reads the places its type keeps files: an
|
|
942
|
+
* item's `media`, each multiple-choice option's `media`, and dictation's and
|
|
943
|
+
* read-aloud's `slowMedia`. In each it lists the main file, `captionsUrl`,
|
|
944
|
+
* every track's `src` and `poster`. A type you register declares its own with
|
|
945
|
+
* `mediaSlots`; without that, its `media` is read.
|
|
946
|
+
*
|
|
947
|
+
* A main file with no address yet is listed with `url: null`, so a recording
|
|
948
|
+
* still to be made has a place in the list. Not read: addresses inside an
|
|
949
|
+
* `…Html` sidecar (the SDK does not parse HTML) and a learner's recordings,
|
|
950
|
+
* which are responses rather than content.
|
|
951
|
+
*
|
|
952
|
+
* ```ts
|
|
953
|
+
* const files = listMediaRefs(entry).filter((ref) => ref.url !== null);
|
|
954
|
+
* ```
|
|
955
|
+
*/
|
|
956
|
+
function listMediaRefs(entry) {
|
|
957
|
+
if (!isRecord$3(entry)) return [];
|
|
958
|
+
if (entry.type !== "item-group") return activityRefs(entry, []);
|
|
959
|
+
const refs = [];
|
|
960
|
+
const stimulus = entry.stimulus;
|
|
961
|
+
if (isRecord$3(stimulus)) {
|
|
962
|
+
const owner = typeof stimulus.id === "string" ? { stimulusId: stimulus.id } : {};
|
|
963
|
+
refs.push(...holderRefs(stimulus.media, ["stimulus", "media"], void 0, owner));
|
|
964
|
+
}
|
|
965
|
+
if (Array.isArray(entry.items)) entry.items.forEach((item, index) => {
|
|
966
|
+
refs.push(...activityRefs(item, ["items", index]));
|
|
967
|
+
});
|
|
968
|
+
return refs;
|
|
969
|
+
}
|
|
970
|
+
/** A deep copy of a JSON value. Content is plain data; a cycle cannot be copied. */
|
|
971
|
+
function copyOf(value, seen) {
|
|
972
|
+
if (typeof value !== "object" || value === null) return value;
|
|
973
|
+
if (seen.has(value)) throw new TypeError("mapMediaRefs: the content contains a circular reference.");
|
|
974
|
+
seen.add(value);
|
|
975
|
+
try {
|
|
976
|
+
if (Array.isArray(value)) return value.map((element) => copyOf(element, seen));
|
|
977
|
+
const copy = {};
|
|
978
|
+
for (const [key, field] of Object.entries(value)) copy[key] = copyOf(field, seen);
|
|
979
|
+
return copy;
|
|
980
|
+
} finally {
|
|
981
|
+
seen.delete(value);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* A copy of `entry` with each address {@link listMediaRefs} finds replaced
|
|
986
|
+
* by what `fn` returns for it: files moved into a new folder structure, or an
|
|
987
|
+
* export's file names pointed at where an import stored them. Nothing else
|
|
988
|
+
* changes, and `entry` is never modified: with `(ref) => ref.url`, the copy is
|
|
989
|
+
* deep-equal to it, with the same `canonicalJson`.
|
|
990
|
+
*
|
|
991
|
+
* For a main file with no address yet (`url: null`), a string attaches one
|
|
992
|
+
* and `null` leaves it empty. For an address that is there, `fn` must return
|
|
993
|
+
* a string: removing a file is not mapping.
|
|
994
|
+
*
|
|
995
|
+
* The copy validates as the original did when every address `fn` returns is
|
|
996
|
+
* one the media rule accepts: `https:`, `http:`, `data:`, `blob:`, or
|
|
997
|
+
* root-relative (`/…`). A bundle-relative `media/part1.mp3` is refused.
|
|
998
|
+
*
|
|
999
|
+
* @throws TypeError when `fn` returns something other than a string or
|
|
1000
|
+
* `null`, or `null` for an address that is there, or when `entry` is circular.
|
|
1001
|
+
*/
|
|
1002
|
+
function mapMediaRefs(entry, fn) {
|
|
1003
|
+
const refs = listMediaRefs(entry);
|
|
1004
|
+
const copy = copyOf(entry, /* @__PURE__ */ new Set());
|
|
1005
|
+
for (const ref of refs) {
|
|
1006
|
+
const next = fn(ref);
|
|
1007
|
+
if (next === null) {
|
|
1008
|
+
if (ref.url !== null) throw new TypeError(`mapMediaRefs: ${ref.path.join(".")} has a file; returning null would remove it, which is not mapping.`);
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
if (typeof next !== "string") throw new TypeError(`mapMediaRefs: the function must return a string or null for ${ref.path.join(".")}, not ${typeof next}.`);
|
|
1012
|
+
if (next === ref.url) continue;
|
|
1013
|
+
const holder = valueAt(copy, ref.path.slice(0, -1));
|
|
1014
|
+
holder[ref.path[ref.path.length - 1]] = next;
|
|
1015
|
+
}
|
|
1016
|
+
return copy;
|
|
1017
|
+
}
|
|
1018
|
+
/** A holder's own text: its transcript, else its script as "Speaker: text" lines. */
|
|
1019
|
+
function ownText(holder) {
|
|
1020
|
+
if (typeof holder.transcript === "string" && holder.transcript.trim() !== "") return holder.transcript;
|
|
1021
|
+
const turns = Array.isArray(holder.script) ? holder.script.flatMap((turn) => isRecord$3(turn) && typeof turn.speaker === "string" && typeof turn.text === "string" && turn.text.trim() !== "" ? [`${turn.speaker.trim()}: ${turn.text.trim()}`] : []) : [];
|
|
1022
|
+
return turns.length > 0 ? turns.join("\n") : null;
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* The author-side text of a recording: its `transcript`, or else its
|
|
1026
|
+
* `script` as one "Speaker: text" line per turn. `null` when it has neither.
|
|
1027
|
+
*
|
|
1028
|
+
* `holder` is a media object, a stimulus, or an activity. A stimulus or an
|
|
1029
|
+
* activity is read for its own text first and then for its `media`'s; a
|
|
1030
|
+
* stimulus keeps both at its own level. Author-only: pass it to the author's
|
|
1031
|
+
* requests (`aiDraftsRequest`'s `source`, a critique), never to a learner's.
|
|
1032
|
+
*
|
|
1033
|
+
* ```ts
|
|
1034
|
+
* const text = transcriptOf(item.media);
|
|
1035
|
+
* if (text !== null) aiDraftsRequest({ types: ['multiple-choice'], source: { kind: 'transcript', text } });
|
|
1036
|
+
* ```
|
|
1037
|
+
*/
|
|
1038
|
+
function transcriptOf(holder) {
|
|
1039
|
+
if (!isRecord$3(holder)) return null;
|
|
1040
|
+
return ownText(holder) ?? (isRecord$3(holder.media) ? ownText(holder.media) : null);
|
|
1041
|
+
}
|
|
1042
|
+
// src/authoring/recordings.ts
|
|
1043
|
+
const written$1 = (value) => typeof value === "string" && value.trim() !== "";
|
|
1044
|
+
/** The script's turns that have a speaker and words, or none. */
|
|
1045
|
+
function turnsOf(script) {
|
|
1046
|
+
return Array.isArray(script) ? script.flatMap((turn) => isRecord$6(turn) && typeof turn.speaker === "string" && written$1(turn.text) ? [{
|
|
1047
|
+
speaker: turn.speaker,
|
|
1048
|
+
text: turn.text
|
|
1049
|
+
}] : []) : [];
|
|
1050
|
+
}
|
|
1051
|
+
const escaped = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1052
|
+
/** A transcript's words, with each `Speaker:` label of the script's speakers taken out. */
|
|
1053
|
+
function transcriptWords(transcript, speakers) {
|
|
1054
|
+
const labels = [...new Set(speakers.map((speaker) => speaker.trim()).filter(written$1))];
|
|
1055
|
+
const unlabelled = labels.length === 0 ? transcript : transcript.replace(new RegExp(`(^|\\s)(?:${labels.map(escaped).join("|")})\\s*:`, "giu"), "$1");
|
|
1056
|
+
return fold(unlabelled);
|
|
1057
|
+
}
|
|
1058
|
+
/** What a recording says, as words: its transcript, or else its script's turns. */
|
|
1059
|
+
function spokenWords(holder) {
|
|
1060
|
+
const turns = turnsOf(holder.script);
|
|
1061
|
+
if (written$1(holder.transcript)) return transcriptWords(holder.transcript, turns.map((turn) => turn.speaker));
|
|
1062
|
+
return turns.length > 0 ? fold(turns.map((turn) => turn.text).join(" ")) : null;
|
|
1063
|
+
}
|
|
1064
|
+
const sameWords = (a, b) => a.length === b.length && a.every((word, index) => word === b[index]);
|
|
1065
|
+
/** One object's own rules: text on media with nothing to say, and a transcript its script contradicts. */
|
|
1066
|
+
function holderFindings(holder, path) {
|
|
1067
|
+
if (!isRecord$6(holder)) return [];
|
|
1068
|
+
const findings = [];
|
|
1069
|
+
const turns = turnsOf(holder.script);
|
|
1070
|
+
if ((written$1(holder.transcript) || turns.length > 0) && (holder.type === "image" || holder.type === "embed")) findings.push(finding("media_transcript_unused", [...path, written$1(holder.transcript) ? "transcript" : "script"], `A transcript or a script on ${holder.type === "image" ? "a picture" : "an embedded player"} has nothing to describe, and nothing reads it. Put it on the recording it belongs to.`));
|
|
1071
|
+
if (written$1(holder.transcript) && turns.length > 0) {
|
|
1072
|
+
const fromTranscript = transcriptWords(holder.transcript, turns.map((turn) => turn.speaker));
|
|
1073
|
+
if (!sameWords(fromTranscript, fold(turns.map((turn) => turn.text).join(" ")))) findings.push(finding("media_script_differs", [...path, "script"], "The script and the transcript say different words. AI facts read the transcript, and a voice reads the script: make them say the same."));
|
|
1074
|
+
}
|
|
1075
|
+
return findings;
|
|
1076
|
+
}
|
|
1077
|
+
/** The field a type's own recording reads out, for the types that have one. */
|
|
1078
|
+
const SPOKEN_FIELD = {
|
|
1079
|
+
dictation: "transcript",
|
|
1080
|
+
"read-aloud": "referenceText"
|
|
1081
|
+
};
|
|
1082
|
+
/**
|
|
1083
|
+
* An item's recording rules, at every place its type keeps media. A
|
|
1084
|
+
* dictation's and a read-aloud's own text is what their recording says, so a
|
|
1085
|
+
* recording that says other words is reported too.
|
|
1086
|
+
*/
|
|
1087
|
+
function recordingFindings(item) {
|
|
1088
|
+
const findings = listMediaRefs(item).filter((ref) => ref.path[ref.path.length - 1] === "url").flatMap((ref) => holderFindings(ref.holder, ref.path.slice(0, -1)));
|
|
1089
|
+
const field = typeof item.type === "string" ? SPOKEN_FIELD[item.type] : void 0;
|
|
1090
|
+
const media = item.media;
|
|
1091
|
+
if (field === void 0 || !isRecord$6(media)) return findings;
|
|
1092
|
+
const said = spokenWords(media);
|
|
1093
|
+
const accepted = [item[field], ...item.type === "dictation" && Array.isArray(item.acceptedTranscripts) ? item.acceptedTranscripts : []].filter(written$1);
|
|
1094
|
+
if (said !== null && accepted.length > 0 && !accepted.some((text) => sameWords(said, fold(text)))) findings.push(finding("media_transcript_differs", ["media", written$1(media.transcript) ? "transcript" : "script"], `The recording's transcript says different words from the item's \`${field}\`, which is what the recording is meant to say.`));
|
|
1095
|
+
return findings;
|
|
1096
|
+
}
|
|
1097
|
+
/** A group's material: its own transcript against its script, and its media's rules. */
|
|
1098
|
+
function stimulusFindings(stimulus) {
|
|
1099
|
+
if (!isRecord$6(stimulus)) return [];
|
|
1100
|
+
return [...holderFindings({
|
|
1101
|
+
transcript: stimulus.transcript,
|
|
1102
|
+
script: stimulus.script
|
|
1103
|
+
}, ["stimulus"]), ...holderFindings(stimulus.media, ["stimulus", "media"])];
|
|
1104
|
+
}
|
|
1105
|
+
// src/authoring/item-group.ts
|
|
870
1106
|
/**
|
|
871
1107
|
* A new, empty reading or listening testlet for an editor to start from.
|
|
872
1108
|
*
|
|
@@ -961,9 +1197,13 @@ function createInteractiveVideoDraft(context) {
|
|
|
961
1197
|
* {@link validateItemGroupDraft} already reports it.
|
|
962
1198
|
*/
|
|
963
1199
|
function critiqueItemGroupDraft(draft) {
|
|
964
|
-
if (!isRecord$
|
|
1200
|
+
if (!isRecord$6(draft)) return [];
|
|
965
1201
|
const items = Array.isArray(draft.items) ? draft.items : [];
|
|
966
|
-
return withValidationScope(() => [
|
|
1202
|
+
return withValidationScope(() => [
|
|
1203
|
+
...stimulusFindings(draft.stimulus),
|
|
1204
|
+
...items.flatMap((item, index) => repathed(critiqueItem(item), ["items", index])),
|
|
1205
|
+
...keyPositionFinding(items, ["items"])
|
|
1206
|
+
]);
|
|
967
1207
|
}
|
|
968
1208
|
/**
|
|
969
1209
|
* The item critic over a question set, as `<ActivitySequence>` takes one: each
|
|
@@ -973,11 +1213,11 @@ function critiqueItemGroupDraft(draft) {
|
|
|
973
1213
|
* skipped.
|
|
974
1214
|
*/
|
|
975
1215
|
function critiqueDrafts(entries) {
|
|
976
|
-
return withValidationScope(() => [...entries.flatMap((entry, index) => repathed(isRecord$
|
|
1216
|
+
return withValidationScope(() => [...entries.flatMap((entry, index) => repathed(isRecord$6(entry) && entry.type === "item-group" ? critiqueItemGroupDraft(entry) : critiqueItem(entry), [index])), ...keyPositionFinding(entries, [])]);
|
|
977
1217
|
}
|
|
978
1218
|
/** One item's findings, or none for an item the critic cannot place. */
|
|
979
1219
|
function critiqueItem(item) {
|
|
980
|
-
if (!isRecord$
|
|
1220
|
+
if (!isRecord$6(item) || typeof item.type !== "string") return [];
|
|
981
1221
|
if (getActivityTypeDescriptor(item.type) === void 0) return [];
|
|
982
1222
|
return critiqueDraft(item.type, item);
|
|
983
1223
|
}
|
|
@@ -990,7 +1230,7 @@ function validateItemGroupDraft(draft) {
|
|
|
990
1230
|
}
|
|
991
1231
|
function validateItemGroupDraftInScope(draft) {
|
|
992
1232
|
const issues = [];
|
|
993
|
-
if (!isRecord$
|
|
1233
|
+
if (!isRecord$6(draft)) {
|
|
994
1234
|
issues.push(issue("ig_not_an_object", [], "A group must be an object with a stimulus and its items."));
|
|
995
1235
|
return {
|
|
996
1236
|
status: "invalid",
|
|
@@ -998,7 +1238,7 @@ function validateItemGroupDraftInScope(draft) {
|
|
|
998
1238
|
};
|
|
999
1239
|
}
|
|
1000
1240
|
issues.push(...checkGroupIdentity(draft));
|
|
1001
|
-
if (isRecord$
|
|
1241
|
+
if (isRecord$6(draft.stimulus)) issues.push(...checkStimulus(draft.stimulus));
|
|
1002
1242
|
else if (isUnset(draft.stimulus)) issues.push(issue("ig_stimulus_required", ["stimulus"], "Add the passage, recording or image the questions are about."));
|
|
1003
1243
|
issues.push(...checkItems(draft));
|
|
1004
1244
|
if (draft.timeline !== void 0 && draft.timeline !== null) issues.push(...checkTimeline(draft));
|
|
@@ -1048,10 +1288,10 @@ function validateItemGroupDraftInScope(draft) {
|
|
|
1048
1288
|
function checkTimeline(draft) {
|
|
1049
1289
|
const issues = [];
|
|
1050
1290
|
const timeline = draft.timeline;
|
|
1051
|
-
if (!isRecord$
|
|
1052
|
-
const stimulus = isRecord$
|
|
1291
|
+
if (!isRecord$6(timeline)) return issues;
|
|
1292
|
+
const stimulus = isRecord$6(draft.stimulus) ? draft.stimulus : void 0;
|
|
1053
1293
|
if (stimulus !== void 0 && !isUnwritten(stimulus.kind) && stimulus.kind !== "video") issues.push(issue("ig_timeline_stimulus_kind", ["stimulus", "kind"], "An interactive video needs a video: quizzes open at moments of it."));
|
|
1054
|
-
else if (stimulus !== void 0 && isRecord$
|
|
1294
|
+
else if (stimulus !== void 0 && isRecord$6(stimulus.media) && !isUnwritten(stimulus.media.type) && stimulus.media.type !== "video") issues.push(issue("ig_timeline_stimulus_kind", [
|
|
1055
1295
|
"stimulus",
|
|
1056
1296
|
"media",
|
|
1057
1297
|
"type"
|
|
@@ -1060,7 +1300,7 @@ function checkTimeline(draft) {
|
|
|
1060
1300
|
const items = Array.isArray(draft.items) ? draft.items : [];
|
|
1061
1301
|
const itemIndexById = /* @__PURE__ */ new Map();
|
|
1062
1302
|
items.forEach((item, index) => {
|
|
1063
|
-
if (!isRecord$
|
|
1303
|
+
if (!isRecord$6(item)) return;
|
|
1064
1304
|
if (typeof item.id === "string" && item.id !== "") itemIndexById.set(item.id, index);
|
|
1065
1305
|
const type = item.type;
|
|
1066
1306
|
if (typeof type === "string" && type !== "" && type !== "item-group" && !isInteractiveVideoItemType(type) && getActivityTypeDescriptor(type) !== void 0) issues.push(issue("ig_timeline_item_type", [
|
|
@@ -1078,7 +1318,7 @@ function checkTimeline(draft) {
|
|
|
1078
1318
|
const quizIds = /* @__PURE__ */ new Set();
|
|
1079
1319
|
const placed = /* @__PURE__ */ new Set();
|
|
1080
1320
|
cues.forEach((cue, cueIndex) => {
|
|
1081
|
-
if (!isRecord$
|
|
1321
|
+
if (!isRecord$6(cue)) return;
|
|
1082
1322
|
const at = (field, ...rest) => [
|
|
1083
1323
|
"timeline",
|
|
1084
1324
|
"cues",
|
|
@@ -1109,7 +1349,7 @@ function checkTimeline(draft) {
|
|
|
1109
1349
|
const chapters = Array.isArray(timeline.chapters) ? timeline.chapters : [];
|
|
1110
1350
|
let previousAt;
|
|
1111
1351
|
chapters.forEach((chapter, index) => {
|
|
1112
|
-
if (!isRecord$
|
|
1352
|
+
if (!isRecord$6(chapter)) return;
|
|
1113
1353
|
if (isUnwritten(chapter.title)) issues.push(issue("ig_timeline_chapter_title", [
|
|
1114
1354
|
"timeline",
|
|
1115
1355
|
"chapters",
|
|
@@ -1170,14 +1410,14 @@ function checkStimulus(stimulus) {
|
|
|
1170
1410
|
if (isUnwritten(kind)) issues.push(issue("ig_stimulus_kind_required", at("kind"), "Choose what the questions are about: a text, a recording, a video, an image, or a mix."));
|
|
1171
1411
|
else if (typeof kind !== "string" || !KINDS$1.has(kind)) {
|
|
1172
1412
|
if (typeof kind === "string") issues.push(issue("ig_stimulus_kind_invalid", at("kind"), `"${kind}" is not a stimulus kind. Use text, audio, video, image or mixed.`));
|
|
1173
|
-
if (isRecord$
|
|
1413
|
+
if (isRecord$6(stimulus.media)) issues.push(...checkMedia(stimulus.media, at("media")));
|
|
1174
1414
|
return issues;
|
|
1175
1415
|
}
|
|
1176
1416
|
const hasBody = typeof stimulus.body === "string" && stimulus.body.trim() !== "";
|
|
1177
1417
|
const htmlPresent = !isUnset(stimulus.bodyHtml);
|
|
1178
1418
|
if (!hasBody && (kind === "text" || kind === "mixed" || htmlPresent)) issues.push(issue("ig_stimulus_body_required", at("body"), 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."));
|
|
1179
1419
|
if (kind !== "text" && isUnset(stimulus.media)) issues.push(issue("ig_stimulus_media_required", at("media"), "Add the recording, video or image."));
|
|
1180
|
-
else if (isRecord$
|
|
1420
|
+
else if (isRecord$6(stimulus.media)) {
|
|
1181
1421
|
const allowed = MEDIA_FOR_KIND[kind];
|
|
1182
1422
|
const mediaType = stimulus.media.type;
|
|
1183
1423
|
if (allowed !== void 0 && typeof mediaType === "string" && !allowed.includes(mediaType)) issues.push(issue("ig_stimulus_media_kind", at("media", "type"), `A "${kind}" stimulus cannot carry ${mediaType} media. Use ${allowed.join(" or ")}.`));
|
|
@@ -1204,7 +1444,7 @@ function checkItems(draft) {
|
|
|
1204
1444
|
const seen = /* @__PURE__ */ new Set();
|
|
1205
1445
|
const repeated = /* @__PURE__ */ new Set();
|
|
1206
1446
|
for (const [index, item] of items.entries()) {
|
|
1207
|
-
if (!isRecord$
|
|
1447
|
+
if (!isRecord$6(item)) continue;
|
|
1208
1448
|
const ordinal = index + 1;
|
|
1209
1449
|
if (isMissingId(item.id)) issues.push(issue("ig_item_id_required", [
|
|
1210
1450
|
"items",
|
|
@@ -1256,8 +1496,7 @@ function checkItems(draft) {
|
|
|
1256
1496
|
for (const id of repeated) issues.push(issue("ig_item_id_duplicate", ["items"], `More than one question has the id "${id}".`));
|
|
1257
1497
|
return issues;
|
|
1258
1498
|
}
|
|
1259
|
-
|
|
1260
|
-
//#region src/authoring/index.ts
|
|
1499
|
+
// src/authoring/index.ts
|
|
1261
1500
|
/**
|
|
1262
1501
|
* Tells a draft that is not finished from a draft that is wrong.
|
|
1263
1502
|
*
|
|
@@ -1301,7 +1540,7 @@ function validateDraft(type, draft) {
|
|
|
1301
1540
|
function validateDraftInScope(type, draft) {
|
|
1302
1541
|
const descriptor = getActivityTypeDescriptor(type);
|
|
1303
1542
|
if (descriptor === void 0) throw new UnknownActivityTypeError(String(type));
|
|
1304
|
-
const reported = isRecord$
|
|
1543
|
+
const reported = isRecord$6(draft) && descriptor.authoring?.checkDraft !== void 0 ? descriptor.authoring.checkDraft(draft).map(normalise) : [];
|
|
1305
1544
|
const issues = [...reported];
|
|
1306
1545
|
const parsed = readSchema(descriptor.schema, draft, `Activity type "${descriptor.type}"`, { reportInput: true });
|
|
1307
1546
|
if (!parsed.success) {
|
|
@@ -1410,12 +1649,11 @@ function normalise(found) {
|
|
|
1410
1649
|
function critiqueDraft(type, draft) {
|
|
1411
1650
|
const descriptor = getActivityTypeDescriptor(type);
|
|
1412
1651
|
if (descriptor === void 0) throw new UnknownActivityTypeError(String(type));
|
|
1652
|
+
if (!isRecord$6(draft)) return [];
|
|
1413
1653
|
const critique = descriptor.authoring?.critique;
|
|
1414
|
-
|
|
1415
|
-
return withValidationScope(() => critique(draft)).map(normaliseFinding);
|
|
1654
|
+
return withValidationScope(() => [...critique === void 0 ? [] : critique(draft), ...recordingFindings(draft)]).map(normaliseFinding);
|
|
1416
1655
|
}
|
|
1417
|
-
|
|
1418
|
-
//#region src/ai-critique.ts
|
|
1656
|
+
// src/ai-critique.ts
|
|
1419
1657
|
/** The most findings one critique may carry. More is refused, not cut. */
|
|
1420
1658
|
const AI_CRITIQUE_MAX_FINDINGS = 20;
|
|
1421
1659
|
/** The longest message or quote in a finding, in code points. */
|
|
@@ -1448,6 +1686,10 @@ const NOT_WORDS = /* @__PURE__ */ new Set([
|
|
|
1448
1686
|
"bankId",
|
|
1449
1687
|
"url",
|
|
1450
1688
|
"captionsUrl",
|
|
1689
|
+
"src",
|
|
1690
|
+
"srclang",
|
|
1691
|
+
"poster",
|
|
1692
|
+
"speaker",
|
|
1451
1693
|
"match",
|
|
1452
1694
|
"playback",
|
|
1453
1695
|
"recording",
|
|
@@ -1459,10 +1701,10 @@ const NOT_WORDS = /* @__PURE__ */ new Set([
|
|
|
1459
1701
|
"timeline"
|
|
1460
1702
|
]);
|
|
1461
1703
|
/**
|
|
1462
|
-
* Every string an author wrote into an item, with its path: the
|
|
1463
|
-
* options, the passage, the answers, the hints, the
|
|
1464
|
-
* `alt`. Settings, ids and addresses are left out, and
|
|
1465
|
-
* sidecar: the plain field beside it holds the same words.
|
|
1704
|
+
* Every string an author wrote into an item, with its path: the instructions,
|
|
1705
|
+
* the question, the options, the passage, the answers, the hints, the
|
|
1706
|
+
* feedback, a picture's `alt`. Settings, ids and addresses are left out, and
|
|
1707
|
+
* so is any `…Html` sidecar: the plain field beside it holds the same words.
|
|
1466
1708
|
*/
|
|
1467
1709
|
function fieldsOf(value, path = []) {
|
|
1468
1710
|
if (typeof value === "string") return value.trim() === "" ? [] : [{
|
|
@@ -1583,8 +1825,7 @@ function checkAiCritique(raw, request) {
|
|
|
1583
1825
|
}
|
|
1584
1826
|
};
|
|
1585
1827
|
}
|
|
1586
|
-
|
|
1587
|
-
//#region src/ai-drafts.ts
|
|
1828
|
+
// src/ai-drafts.ts
|
|
1588
1829
|
/** The most drafts one request may ask for, and the most a reply may hold when it names no count. */
|
|
1589
1830
|
const AI_DRAFTS_MAX_COUNT = 50;
|
|
1590
1831
|
/** The most repair passes `generateDrafts` makes. */
|
|
@@ -1676,7 +1917,7 @@ const FIELDS = {
|
|
|
1676
1917
|
},
|
|
1677
1918
|
transcript: text("What the learner hears and types: words from the source, as they are said there."),
|
|
1678
1919
|
referenceText: text("What the learner reads aloud: a sentence or two from the source."),
|
|
1679
|
-
instructions: text("Optional.
|
|
1920
|
+
instructions: text("Optional. The task, shown to the learner before the item: what to do (\"Complete the sentence. Use ONE word.\"), or how to read it aloud. Never part of the text the learner answers."),
|
|
1680
1921
|
prompt: text("The writing task, as plain text."),
|
|
1681
1922
|
minWords: {
|
|
1682
1923
|
type: "integer",
|
|
@@ -1708,6 +1949,7 @@ const FIELDS = {
|
|
|
1708
1949
|
const WRITES = {
|
|
1709
1950
|
"multiple-choice": [
|
|
1710
1951
|
"title",
|
|
1952
|
+
"instructions",
|
|
1711
1953
|
"question",
|
|
1712
1954
|
"mode",
|
|
1713
1955
|
"options",
|
|
@@ -1715,12 +1957,14 @@ const WRITES = {
|
|
|
1715
1957
|
],
|
|
1716
1958
|
"fill-in-the-blanks": [
|
|
1717
1959
|
"title",
|
|
1960
|
+
"instructions",
|
|
1718
1961
|
"passage",
|
|
1719
1962
|
"blanks",
|
|
1720
1963
|
"feedback"
|
|
1721
1964
|
],
|
|
1722
1965
|
"gap-select": [
|
|
1723
1966
|
"title",
|
|
1967
|
+
"instructions",
|
|
1724
1968
|
"passage",
|
|
1725
1969
|
"gaps",
|
|
1726
1970
|
"feedback"
|
|
@@ -1858,10 +2102,16 @@ function feedbackOf(entry) {
|
|
|
1858
2102
|
...incorrect !== void 0 ? { incorrect } : {}
|
|
1859
2103
|
} };
|
|
1860
2104
|
}
|
|
2105
|
+
/** The instructions a model wrote for the learner, when it wrote any. */
|
|
2106
|
+
function instructionsOf(entry) {
|
|
2107
|
+
const instructions = written(entry.instructions);
|
|
2108
|
+
return instructions !== void 0 ? { instructions } : {};
|
|
2109
|
+
}
|
|
1861
2110
|
/** A type's content, read from what a model wrote. */
|
|
1862
2111
|
function contentOf(type, entry, newId) {
|
|
1863
2112
|
switch (type) {
|
|
1864
2113
|
case "multiple-choice": return {
|
|
2114
|
+
...instructionsOf(entry),
|
|
1865
2115
|
question: stringOf(entry.question),
|
|
1866
2116
|
...typeof entry.mode === "string" ? { mode: entry.mode } : {},
|
|
1867
2117
|
options: listOf(entry.options).map((option) => {
|
|
@@ -1877,6 +2127,7 @@ function contentOf(type, entry, newId) {
|
|
|
1877
2127
|
...feedbackOf(entry)
|
|
1878
2128
|
};
|
|
1879
2129
|
case "fill-in-the-blanks": return {
|
|
2130
|
+
...instructionsOf(entry),
|
|
1880
2131
|
passage: stringOf(entry.passage),
|
|
1881
2132
|
blanks: listOf(entry.blanks).map((blank, index) => {
|
|
1882
2133
|
const fields = isRecord$1(blank) ? blank : {};
|
|
@@ -1890,6 +2141,7 @@ function contentOf(type, entry, newId) {
|
|
|
1890
2141
|
...feedbackOf(entry)
|
|
1891
2142
|
};
|
|
1892
2143
|
case "gap-select": return {
|
|
2144
|
+
...instructionsOf(entry),
|
|
1893
2145
|
passage: stringOf(entry.passage),
|
|
1894
2146
|
gaps: listOf(entry.gaps).map((gap, index) => {
|
|
1895
2147
|
const marked = listOf(isRecord$1(gap) ? gap.choices : void 0);
|
|
@@ -1910,14 +2162,11 @@ function contentOf(type, entry, newId) {
|
|
|
1910
2162
|
transcript: stringOf(entry.transcript),
|
|
1911
2163
|
...feedbackOf(entry)
|
|
1912
2164
|
};
|
|
1913
|
-
case "read-aloud": {
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
...feedbackOf(entry)
|
|
1919
|
-
};
|
|
1920
|
-
}
|
|
2165
|
+
case "read-aloud": return {
|
|
2166
|
+
referenceText: stringOf(entry.referenceText),
|
|
2167
|
+
...instructionsOf(entry),
|
|
2168
|
+
...feedbackOf(entry)
|
|
2169
|
+
};
|
|
1921
2170
|
default: {
|
|
1922
2171
|
const criteria = listOf(isRecord$1(entry.rubric) ? entry.rubric.criteria : void 0);
|
|
1923
2172
|
return {
|
|
@@ -2288,8 +2537,7 @@ function interactiveVideoFromDrafts(input) {
|
|
|
2288
2537
|
findings: critiqueItemGroupDraft(group)
|
|
2289
2538
|
};
|
|
2290
2539
|
}
|
|
2291
|
-
|
|
2292
|
-
//#region src/ai-written.ts
|
|
2540
|
+
// src/ai-written.ts
|
|
2293
2541
|
/**
|
|
2294
2542
|
* What the AI features about a written response share: reading a model's text
|
|
2295
2543
|
* fields, and finding each correction's quote in the learner's text.
|
|
@@ -2432,8 +2680,7 @@ function checkCorrections(raw, text) {
|
|
|
2432
2680
|
corrections
|
|
2433
2681
|
};
|
|
2434
2682
|
}
|
|
2435
|
-
|
|
2436
|
-
//#region src/ai-grading.ts
|
|
2683
|
+
// src/ai-grading.ts
|
|
2437
2684
|
/**
|
|
2438
2685
|
* How far a model's level probabilities may sum from 1 and still be a
|
|
2439
2686
|
* distribution: rounding in a model's output, not a different judgement.
|
|
@@ -2757,8 +3004,7 @@ function checkAiGrading(raw, request) {
|
|
|
2757
3004
|
}
|
|
2758
3005
|
};
|
|
2759
3006
|
}
|
|
2760
|
-
|
|
2761
|
-
//#region src/ai-writing.ts
|
|
3007
|
+
// src/ai-writing.ts
|
|
2762
3008
|
/**
|
|
2763
3009
|
* The request for feedback on a draft, or `null` when there is none to give:
|
|
2764
3010
|
* not a written response, an author who switched explanations off (writing
|
|
@@ -2928,7 +3174,6 @@ function checkAiWritingFeedback(raw, request) {
|
|
|
2928
3174
|
}
|
|
2929
3175
|
};
|
|
2930
3176
|
}
|
|
2931
|
-
|
|
2932
|
-
export { checkAiCoaching as A, checkAiExplanation as B, createItemGroupDraft as C, AI_COACHING_MAX_TIP_LENGTH as D, validateItemGroupDraft as E, aiExplanationRequest as F, hintRevealsAnswer as H, aiGradeOf as I, aiHintRequest as L, AI_HINT_TYPES as M, AI_TEXT_MAX_LENGTH as N, AI_COACHING_MAX_WORDS as O, aiAllowedByContent as P, aiSupports as R, createInteractiveVideoDraft as S, critiqueItemGroupDraft as T, readAiTextResult as U, checkAiHint as V, AI_MATERIAL_MAX_LENGTH as W, aiCritiqueRequest as _, checkAiGrading as a, critiqueDraft as b, AI_DRAFTS_MAX_COUNT as c, aiDraftsRequest as d, checkAiDrafts as f, AI_CRITIQUE_MAX_FINDINGS as g, AI_CRITIQUE_MAX_FIELD_LENGTH as h, aiGradingRequest as i, AI_EXPLANATION_TYPES as j, aiCoachingRequest as k, AI_DRAFTS_MAX_REPAIRS as l, interactiveVideoFromDrafts as m, checkAiWritingFeedback as n, AI_WRITING_MAX_CORRECTIONS as o, generateDrafts as p, AI_GRADING_PROBABILITY_TOLERANCE as r, AI_WRITING_MAX_FIELD_LENGTH as s, aiWritingFeedbackRequest as t, aiDraftsRepairRequest as u, checkAiCritique as v, critiqueDrafts as w, validateDraft as x, createDraft as y, buildAiFacts as z };
|
|
3177
|
+
export { AI_COACHING_MAX_TIP_LENGTH as A, aiHintRequest as B, createItemGroupDraft as C, listMediaRefs as D, validateItemGroupDraft as E, AI_HINT_TYPES as F, hintRevealsAnswer as G, buildAiFacts as H, AI_TEXT_MAX_LENGTH as I, readAiTextResult as K, aiAllowedByContent as L, aiCoachingRequest as M, checkAiCoaching as N, mapMediaRefs as O, AI_EXPLANATION_TYPES as P, aiExplanationRequest as R, createInteractiveVideoDraft as S, critiqueItemGroupDraft as T, checkAiExplanation as U, aiSupports as V, checkAiHint as W, aiCritiqueRequest as _, checkAiGrading as a, critiqueDraft as b, AI_DRAFTS_MAX_COUNT as c, aiDraftsRequest as d, checkAiDrafts as f, AI_CRITIQUE_MAX_FINDINGS as g, AI_CRITIQUE_MAX_FIELD_LENGTH as h, aiGradingRequest as i, AI_COACHING_MAX_WORDS as j, transcriptOf as k, AI_DRAFTS_MAX_REPAIRS as l, interactiveVideoFromDrafts as m, checkAiWritingFeedback as n, AI_WRITING_MAX_CORRECTIONS as o, generateDrafts as p, AI_MATERIAL_MAX_LENGTH as q, AI_GRADING_PROBABILITY_TOLERANCE as r, AI_WRITING_MAX_FIELD_LENGTH as s, aiWritingFeedbackRequest as t, aiDraftsRepairRequest as u, checkAiCritique as v, critiqueDrafts as w, validateDraft as x, createDraft as y, aiGradeOf as z };
|
|
2933
3178
|
|
|
2934
|
-
//# sourceMappingURL=ai-writing-
|
|
3179
|
+
//# sourceMappingURL=ai-writing-B7oMCt66.js.map
|