@intellectif/lk-core 1.5.0 → 1.6.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 +57 -0
- package/dist/ai-check.cjs +2 -2
- package/dist/ai-check.d.cts +1 -1
- package/dist/ai-check.d.ts +1 -1
- package/dist/ai-check.js +2 -2
- package/dist/{ai-BYryFkF9.d.ts → ai-i3jwXnpX.d.cts} +45 -4
- package/dist/ai-i3jwXnpX.d.cts.map +1 -0
- package/dist/{ai-BYryFkF9.d.cts → ai-i3jwXnpX.d.ts} +45 -4
- package/dist/ai-i3jwXnpX.d.ts.map +1 -0
- package/dist/{ai-writing-Cen7-3qs.cjs → ai-writing-C4YRVrFi.cjs} +275 -15
- package/dist/ai-writing-C4YRVrFi.cjs.map +1 -0
- package/dist/{ai-writing-D8PdhJJz.js → ai-writing-OJ5m_1EB.js} +273 -31
- package/dist/ai-writing-OJ5m_1EB.js.map +1 -0
- package/dist/{index-D7R3Ll8R.d.cts → index--U7hHV0s.d.cts} +2 -2
- package/dist/{index-D7R3Ll8R.d.cts.map → index--U7hHV0s.d.cts.map} +1 -1
- package/dist/{index-qMbxed1B.d.cts → index-CFRgZLhH.d.cts} +41 -12
- package/dist/index-CFRgZLhH.d.cts.map +1 -0
- package/dist/{index-DUaW1Tj5.d.ts → index-DeD8EI1D.d.ts} +2 -2
- package/dist/{index-DUaW1Tj5.d.ts.map → index-DeD8EI1D.d.ts.map} +1 -1
- package/dist/{index-wMc1E_aM.d.ts → index-Dj92T76o.d.ts} +2 -2
- package/dist/{index-wMc1E_aM.d.ts.map → index-Dj92T76o.d.ts.map} +1 -1
- package/dist/{index-Bg0aeWtM.d.cts → index-Uh1u57yq.d.cts} +2 -2
- package/dist/{index-Bg0aeWtM.d.cts.map → index-Uh1u57yq.d.cts.map} +1 -1
- package/dist/{index-CxpiWedW.d.ts → index-WBHSrIUU.d.ts} +41 -12
- package/dist/index-WBHSrIUU.d.ts.map +1 -0
- package/dist/index.cjs +63 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +137 -5
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +137 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -8
- package/dist/index.js.map +1 -1
- package/dist/{item-group-DdSUCUjM.js → item-group-BbvvH_Ob.js} +3 -2
- package/dist/item-group-BbvvH_Ob.js.map +1 -0
- package/dist/{item-group-DEB7nLID.cjs → item-group-CQRbb8Rx.cjs} +3 -2
- package/dist/item-group-CQRbb8Rx.cjs.map +1 -0
- package/dist/{registry-DaZRpqjW.js → registry-B23o9UWh.js} +65 -18
- package/dist/registry-B23o9UWh.js.map +1 -0
- package/dist/{registry-Ce00A_OW.cjs → registry-Ih5n4PVZ.cjs} +82 -17
- package/dist/registry-Ih5n4PVZ.cjs.map +1 -0
- package/dist/{schemas-DksueYsu.cjs → schemas-Q2dyOgc2.cjs} +77 -14
- package/dist/schemas-Q2dyOgc2.cjs.map +1 -0
- package/dist/{schemas-BioKFf3F.js → schemas-yId-bJhD.js} +72 -15
- package/dist/schemas-yId-bJhD.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-BKNNTz-s.js → scoring-CLMdk8i9.js} +2 -2
- package/dist/{scoring-BKNNTz-s.js.map → scoring-CLMdk8i9.js.map} +1 -1
- package/dist/{scoring-lKQVYsxy.cjs → scoring-CaPS6q8b.cjs} +2 -2
- package/dist/{scoring-lKQVYsxy.cjs.map → scoring-CaPS6q8b.cjs.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-BX0hvRRB.cjs} +2 -2
- package/dist/{xapi-NYrTlpID.cjs.map → xapi-BX0hvRRB.cjs.map} +1 -1
- package/dist/{xapi-wR6gu4qS.js → xapi-Cdk9fTvV.js} +2 -2
- package/dist/{xapi-wR6gu4qS.js.map → xapi-Cdk9fTvV.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 +1 -1
- 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-CxpiWedW.d.ts.map +0 -1
- package/dist/index-qMbxed1B.d.cts.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,6 +1,6 @@
|
|
|
1
|
-
const require_registry = require("./registry-
|
|
2
|
-
const require_scoring = require("./scoring-
|
|
3
|
-
const require_item_group = require("./item-group-
|
|
1
|
+
const require_registry = require("./registry-Ih5n4PVZ.cjs");
|
|
2
|
+
const require_scoring = require("./scoring-CaPS6q8b.cjs");
|
|
3
|
+
const require_item_group = require("./item-group-CQRbb8Rx.cjs");
|
|
4
4
|
//#region src/ai-material.ts
|
|
5
5
|
/**
|
|
6
6
|
* What a model is told about the group a question sits in: the material the
|
|
@@ -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 } : {}
|
|
@@ -742,7 +742,7 @@ function aiCoachingRequest(input) {
|
|
|
742
742
|
...learnerLocale !== void 0 ? { learnerLocale } : {}
|
|
743
743
|
};
|
|
744
744
|
}
|
|
745
|
-
const isRecord$
|
|
745
|
+
const isRecord$4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
746
746
|
/** A symbol as it is compared: composed, and without the spaces around it. */
|
|
747
747
|
const symbolKey = (symbol) => symbol.normalize("NFC").trim();
|
|
748
748
|
/**
|
|
@@ -791,7 +791,7 @@ function checkAiCoaching(raw, request) {
|
|
|
791
791
|
});
|
|
792
792
|
const seen = /* @__PURE__ */ new Set();
|
|
793
793
|
for (const entry of entries) {
|
|
794
|
-
if (!isRecord$
|
|
794
|
+
if (!isRecord$4(entry) || typeof entry.itemId !== "string" || typeof entry.tip !== "string") return {
|
|
795
795
|
ok: false,
|
|
796
796
|
refusal: "malformed"
|
|
797
797
|
};
|
|
@@ -839,7 +839,7 @@ function checkAiCoaching(raw, request) {
|
|
|
839
839
|
/** A sound a model names, checked against the sounds the engine reported for the word. */
|
|
840
840
|
function checkSound(raw, fact) {
|
|
841
841
|
if (raw === void 0 || raw === null) return;
|
|
842
|
-
if (!isRecord$
|
|
842
|
+
if (!isRecord$4(raw) || typeof raw.expected !== "string" || raw.heard !== void 0 && raw.heard !== null && typeof raw.heard !== "string") return {
|
|
843
843
|
ok: false,
|
|
844
844
|
refusal: "malformed"
|
|
845
845
|
};
|
|
@@ -866,6 +866,240 @@ function checkSound(raw, fact) {
|
|
|
866
866
|
};
|
|
867
867
|
}
|
|
868
868
|
//#endregion
|
|
869
|
+
//#region src/media-refs.ts
|
|
870
|
+
const MAIN_KINDS = [
|
|
871
|
+
"audio",
|
|
872
|
+
"video",
|
|
873
|
+
"image",
|
|
874
|
+
"embed"
|
|
875
|
+
];
|
|
876
|
+
function isRecord$3(value) {
|
|
877
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
878
|
+
}
|
|
879
|
+
function valueAt(root, path) {
|
|
880
|
+
let at = root;
|
|
881
|
+
for (const key of path) {
|
|
882
|
+
if (typeof key === "number" ? !Array.isArray(at) : !isRecord$3(at)) return;
|
|
883
|
+
at = at[key];
|
|
884
|
+
}
|
|
885
|
+
return at;
|
|
886
|
+
}
|
|
887
|
+
/** Where `item` keeps its files: what its type declares, or its `media` alone. */
|
|
888
|
+
function slotsOf(item) {
|
|
889
|
+
const declared = typeof item.type === "string" ? require_registry.getActivityTypeDescriptor(item.type)?.mediaSlots : void 0;
|
|
890
|
+
if (declared === void 0) return [{ path: ["media"] }];
|
|
891
|
+
const slots = declared(item);
|
|
892
|
+
return Array.isArray(slots) ? slots : [];
|
|
893
|
+
}
|
|
894
|
+
function holderRefs(holder, path, kindOfSlot, owner) {
|
|
895
|
+
if (!isRecord$3(holder)) return [];
|
|
896
|
+
const main = typeof holder.type === "string" && MAIN_KINDS.includes(holder.type) ? holder.type : kindOfSlot;
|
|
897
|
+
const refs = [];
|
|
898
|
+
const add = (field, kind, url) => {
|
|
899
|
+
refs.push({
|
|
900
|
+
path: [...path, ...field],
|
|
901
|
+
kind,
|
|
902
|
+
url,
|
|
903
|
+
holder,
|
|
904
|
+
...owner
|
|
905
|
+
});
|
|
906
|
+
};
|
|
907
|
+
if (main !== void 0) add(["url"], main, typeof holder.url === "string" ? holder.url : null);
|
|
908
|
+
if (typeof holder.captionsUrl === "string") add(["captionsUrl"], "captions", holder.captionsUrl);
|
|
909
|
+
if (Array.isArray(holder.tracks)) holder.tracks.forEach((track, index) => {
|
|
910
|
+
if (isRecord$3(track) && typeof track.src === "string") refs.push({
|
|
911
|
+
path: [
|
|
912
|
+
...path,
|
|
913
|
+
"tracks",
|
|
914
|
+
index,
|
|
915
|
+
"src"
|
|
916
|
+
],
|
|
917
|
+
kind: "track",
|
|
918
|
+
url: track.src,
|
|
919
|
+
holder: track,
|
|
920
|
+
...owner,
|
|
921
|
+
...typeof track.srclang === "string" ? { srclang: track.srclang } : {}
|
|
922
|
+
});
|
|
923
|
+
});
|
|
924
|
+
if (typeof holder.poster === "string") add(["poster"], "poster", holder.poster);
|
|
925
|
+
return refs;
|
|
926
|
+
}
|
|
927
|
+
function activityRefs(item, base) {
|
|
928
|
+
if (!isRecord$3(item)) return [];
|
|
929
|
+
const owner = typeof item.id === "string" ? { activityId: item.id } : {};
|
|
930
|
+
return slotsOf(item).flatMap((slot) => holderRefs(valueAt(item, slot.path), [...base, ...slot.path], slot.kind, owner));
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Every media address in one piece of content: a loose activity or an item
|
|
934
|
+
* group (its stimulus first, then each item in authored order), as a
|
|
935
|
+
* `SequenceEntry` holds them. Drafts are read too.
|
|
936
|
+
*
|
|
937
|
+
* For each activity the walker reads the places its type keeps files: an
|
|
938
|
+
* item's `media`, each multiple-choice option's `media`, and dictation's and
|
|
939
|
+
* read-aloud's `slowMedia`. In each it lists the main file, `captionsUrl`,
|
|
940
|
+
* every track's `src` and `poster`. A type you register declares its own with
|
|
941
|
+
* `mediaSlots`; without that, its `media` is read.
|
|
942
|
+
*
|
|
943
|
+
* A main file with no address yet is listed with `url: null`, so a recording
|
|
944
|
+
* still to be made has a place in the list. Not read: addresses inside an
|
|
945
|
+
* `…Html` sidecar (the SDK does not parse HTML) and a learner's recordings,
|
|
946
|
+
* which are responses rather than content.
|
|
947
|
+
*
|
|
948
|
+
* ```ts
|
|
949
|
+
* const files = listMediaRefs(entry).filter((ref) => ref.url !== null);
|
|
950
|
+
* ```
|
|
951
|
+
*/
|
|
952
|
+
function listMediaRefs(entry) {
|
|
953
|
+
if (!isRecord$3(entry)) return [];
|
|
954
|
+
if (entry.type !== "item-group") return activityRefs(entry, []);
|
|
955
|
+
const refs = [];
|
|
956
|
+
const stimulus = entry.stimulus;
|
|
957
|
+
if (isRecord$3(stimulus)) {
|
|
958
|
+
const owner = typeof stimulus.id === "string" ? { stimulusId: stimulus.id } : {};
|
|
959
|
+
refs.push(...holderRefs(stimulus.media, ["stimulus", "media"], void 0, owner));
|
|
960
|
+
}
|
|
961
|
+
if (Array.isArray(entry.items)) entry.items.forEach((item, index) => {
|
|
962
|
+
refs.push(...activityRefs(item, ["items", index]));
|
|
963
|
+
});
|
|
964
|
+
return refs;
|
|
965
|
+
}
|
|
966
|
+
/** A deep copy of a JSON value. Content is plain data; a cycle cannot be copied. */
|
|
967
|
+
function copyOf(value, seen) {
|
|
968
|
+
if (typeof value !== "object" || value === null) return value;
|
|
969
|
+
if (seen.has(value)) throw new TypeError("mapMediaRefs: the content contains a circular reference.");
|
|
970
|
+
seen.add(value);
|
|
971
|
+
try {
|
|
972
|
+
if (Array.isArray(value)) return value.map((element) => copyOf(element, seen));
|
|
973
|
+
const copy = {};
|
|
974
|
+
for (const [key, field] of Object.entries(value)) copy[key] = copyOf(field, seen);
|
|
975
|
+
return copy;
|
|
976
|
+
} finally {
|
|
977
|
+
seen.delete(value);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* A copy of `entry` with each address {@link listMediaRefs} finds replaced
|
|
982
|
+
* by what `fn` returns for it: files moved into a new folder structure, or an
|
|
983
|
+
* export's file names pointed at where an import stored them. Nothing else
|
|
984
|
+
* changes, and `entry` is never modified: with `(ref) => ref.url`, the copy is
|
|
985
|
+
* deep-equal to it, with the same `canonicalJson`.
|
|
986
|
+
*
|
|
987
|
+
* For a main file with no address yet (`url: null`), a string attaches one
|
|
988
|
+
* and `null` leaves it empty. For an address that is there, `fn` must return
|
|
989
|
+
* a string: removing a file is not mapping.
|
|
990
|
+
*
|
|
991
|
+
* The copy validates as the original did when every address `fn` returns is
|
|
992
|
+
* one the media rule accepts: `https:`, `http:`, `data:`, `blob:`, or
|
|
993
|
+
* root-relative (`/…`). A bundle-relative `media/part1.mp3` is refused.
|
|
994
|
+
*
|
|
995
|
+
* @throws TypeError when `fn` returns something other than a string or
|
|
996
|
+
* `null`, or `null` for an address that is there, or when `entry` is circular.
|
|
997
|
+
*/
|
|
998
|
+
function mapMediaRefs(entry, fn) {
|
|
999
|
+
const refs = listMediaRefs(entry);
|
|
1000
|
+
const copy = copyOf(entry, /* @__PURE__ */ new Set());
|
|
1001
|
+
for (const ref of refs) {
|
|
1002
|
+
const next = fn(ref);
|
|
1003
|
+
if (next === null) {
|
|
1004
|
+
if (ref.url !== null) throw new TypeError(`mapMediaRefs: ${ref.path.join(".")} has a file; returning null would remove it, which is not mapping.`);
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
if (typeof next !== "string") throw new TypeError(`mapMediaRefs: the function must return a string or null for ${ref.path.join(".")}, not ${typeof next}.`);
|
|
1008
|
+
if (next === ref.url) continue;
|
|
1009
|
+
const holder = valueAt(copy, ref.path.slice(0, -1));
|
|
1010
|
+
holder[ref.path[ref.path.length - 1]] = next;
|
|
1011
|
+
}
|
|
1012
|
+
return copy;
|
|
1013
|
+
}
|
|
1014
|
+
/** A holder's own text: its transcript, else its script as "Speaker: text" lines. */
|
|
1015
|
+
function ownText(holder) {
|
|
1016
|
+
if (typeof holder.transcript === "string" && holder.transcript.trim() !== "") return holder.transcript;
|
|
1017
|
+
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()}`] : []) : [];
|
|
1018
|
+
return turns.length > 0 ? turns.join("\n") : null;
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* The author-side text of a recording: its `transcript`, or else its
|
|
1022
|
+
* `script` as one "Speaker: text" line per turn. `null` when it has neither.
|
|
1023
|
+
*
|
|
1024
|
+
* `holder` is a media object, a stimulus, or an activity. A stimulus or an
|
|
1025
|
+
* activity is read for its own text first and then for its `media`'s; a
|
|
1026
|
+
* stimulus keeps both at its own level. Author-only: pass it to the author's
|
|
1027
|
+
* requests (`aiDraftsRequest`'s `source`, a critique), never to a learner's.
|
|
1028
|
+
*
|
|
1029
|
+
* ```ts
|
|
1030
|
+
* const text = transcriptOf(item.media);
|
|
1031
|
+
* if (text !== null) aiDraftsRequest({ types: ['multiple-choice'], source: { kind: 'transcript', text } });
|
|
1032
|
+
* ```
|
|
1033
|
+
*/
|
|
1034
|
+
function transcriptOf(holder) {
|
|
1035
|
+
if (!isRecord$3(holder)) return null;
|
|
1036
|
+
return ownText(holder) ?? (isRecord$3(holder.media) ? ownText(holder.media) : null);
|
|
1037
|
+
}
|
|
1038
|
+
//#endregion
|
|
1039
|
+
//#region src/authoring/recordings.ts
|
|
1040
|
+
const written$1 = (value) => typeof value === "string" && value.trim() !== "";
|
|
1041
|
+
/** The script's turns that have a speaker and words, or none. */
|
|
1042
|
+
function turnsOf(script) {
|
|
1043
|
+
return Array.isArray(script) ? script.flatMap((turn) => require_registry.isRecord(turn) && typeof turn.speaker === "string" && written$1(turn.text) ? [{
|
|
1044
|
+
speaker: turn.speaker,
|
|
1045
|
+
text: turn.text
|
|
1046
|
+
}] : []) : [];
|
|
1047
|
+
}
|
|
1048
|
+
const escaped = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1049
|
+
/** A transcript's words, with each `Speaker:` label of the script's speakers taken out. */
|
|
1050
|
+
function transcriptWords(transcript, speakers) {
|
|
1051
|
+
const labels = [...new Set(speakers.map((speaker) => speaker.trim()).filter(written$1))];
|
|
1052
|
+
const unlabelled = labels.length === 0 ? transcript : transcript.replace(new RegExp(`(^|\\s)(?:${labels.map(escaped).join("|")})\\s*:`, "giu"), "$1");
|
|
1053
|
+
return require_registry.fold(unlabelled);
|
|
1054
|
+
}
|
|
1055
|
+
/** What a recording says, as words: its transcript, or else its script's turns. */
|
|
1056
|
+
function spokenWords(holder) {
|
|
1057
|
+
const turns = turnsOf(holder.script);
|
|
1058
|
+
if (written$1(holder.transcript)) return transcriptWords(holder.transcript, turns.map((turn) => turn.speaker));
|
|
1059
|
+
return turns.length > 0 ? require_registry.fold(turns.map((turn) => turn.text).join(" ")) : null;
|
|
1060
|
+
}
|
|
1061
|
+
const sameWords = (a, b) => a.length === b.length && a.every((word, index) => word === b[index]);
|
|
1062
|
+
/** One object's own rules: text on media with nothing to say, and a transcript its script contradicts. */
|
|
1063
|
+
function holderFindings(holder, path) {
|
|
1064
|
+
if (!require_registry.isRecord(holder)) return [];
|
|
1065
|
+
const findings = [];
|
|
1066
|
+
const turns = turnsOf(holder.script);
|
|
1067
|
+
if ((written$1(holder.transcript) || turns.length > 0) && (holder.type === "image" || holder.type === "embed")) findings.push(require_registry.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.`));
|
|
1068
|
+
if (written$1(holder.transcript) && turns.length > 0) {
|
|
1069
|
+
const fromTranscript = transcriptWords(holder.transcript, turns.map((turn) => turn.speaker));
|
|
1070
|
+
if (!sameWords(fromTranscript, require_registry.fold(turns.map((turn) => turn.text).join(" ")))) findings.push(require_registry.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."));
|
|
1071
|
+
}
|
|
1072
|
+
return findings;
|
|
1073
|
+
}
|
|
1074
|
+
/** The field a type's own recording reads out, for the types that have one. */
|
|
1075
|
+
const SPOKEN_FIELD = {
|
|
1076
|
+
dictation: "transcript",
|
|
1077
|
+
"read-aloud": "referenceText"
|
|
1078
|
+
};
|
|
1079
|
+
/**
|
|
1080
|
+
* An item's recording rules, at every place its type keeps media. A
|
|
1081
|
+
* dictation's and a read-aloud's own text is what their recording says, so a
|
|
1082
|
+
* recording that says other words is reported too.
|
|
1083
|
+
*/
|
|
1084
|
+
function recordingFindings(item) {
|
|
1085
|
+
const findings = listMediaRefs(item).filter((ref) => ref.path[ref.path.length - 1] === "url").flatMap((ref) => holderFindings(ref.holder, ref.path.slice(0, -1)));
|
|
1086
|
+
const field = typeof item.type === "string" ? SPOKEN_FIELD[item.type] : void 0;
|
|
1087
|
+
const media = item.media;
|
|
1088
|
+
if (field === void 0 || !require_registry.isRecord(media)) return findings;
|
|
1089
|
+
const said = spokenWords(media);
|
|
1090
|
+
const accepted = [item[field], ...item.type === "dictation" && Array.isArray(item.acceptedTranscripts) ? item.acceptedTranscripts : []].filter(written$1);
|
|
1091
|
+
if (said !== null && accepted.length > 0 && !accepted.some((text) => sameWords(said, require_registry.fold(text)))) findings.push(require_registry.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.`));
|
|
1092
|
+
return findings;
|
|
1093
|
+
}
|
|
1094
|
+
/** A group's material: its own transcript against its script, and its media's rules. */
|
|
1095
|
+
function stimulusFindings(stimulus) {
|
|
1096
|
+
if (!require_registry.isRecord(stimulus)) return [];
|
|
1097
|
+
return [...holderFindings({
|
|
1098
|
+
transcript: stimulus.transcript,
|
|
1099
|
+
script: stimulus.script
|
|
1100
|
+
}, ["stimulus"]), ...holderFindings(stimulus.media, ["stimulus", "media"])];
|
|
1101
|
+
}
|
|
1102
|
+
//#endregion
|
|
869
1103
|
//#region src/authoring/item-group.ts
|
|
870
1104
|
/**
|
|
871
1105
|
* A new, empty reading or listening testlet for an editor to start from.
|
|
@@ -963,7 +1197,11 @@ function createInteractiveVideoDraft(context) {
|
|
|
963
1197
|
function critiqueItemGroupDraft(draft) {
|
|
964
1198
|
if (!require_registry.isRecord(draft)) return [];
|
|
965
1199
|
const items = Array.isArray(draft.items) ? draft.items : [];
|
|
966
|
-
return require_registry.withValidationScope(() => [
|
|
1200
|
+
return require_registry.withValidationScope(() => [
|
|
1201
|
+
...stimulusFindings(draft.stimulus),
|
|
1202
|
+
...items.flatMap((item, index) => repathed(critiqueItem(item), ["items", index])),
|
|
1203
|
+
...require_registry.keyPositionFinding(items, ["items"])
|
|
1204
|
+
]);
|
|
967
1205
|
}
|
|
968
1206
|
/**
|
|
969
1207
|
* The item critic over a question set, as `<ActivitySequence>` takes one: each
|
|
@@ -1410,9 +1648,9 @@ function normalise(found) {
|
|
|
1410
1648
|
function critiqueDraft(type, draft) {
|
|
1411
1649
|
const descriptor = require_registry.getActivityTypeDescriptor(type);
|
|
1412
1650
|
if (descriptor === void 0) throw new require_registry.UnknownActivityTypeError(String(type));
|
|
1651
|
+
if (!require_registry.isRecord(draft)) return [];
|
|
1413
1652
|
const critique = descriptor.authoring?.critique;
|
|
1414
|
-
|
|
1415
|
-
return require_registry.withValidationScope(() => critique(draft)).map(require_registry.normaliseFinding);
|
|
1653
|
+
return require_registry.withValidationScope(() => [...critique === void 0 ? [] : critique(draft), ...recordingFindings(draft)]).map(require_registry.normaliseFinding);
|
|
1416
1654
|
}
|
|
1417
1655
|
//#endregion
|
|
1418
1656
|
//#region src/ai-critique.ts
|
|
@@ -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",
|
|
@@ -3175,12 +3417,30 @@ Object.defineProperty(exports, "interactiveVideoFromDrafts", {
|
|
|
3175
3417
|
return interactiveVideoFromDrafts;
|
|
3176
3418
|
}
|
|
3177
3419
|
});
|
|
3420
|
+
Object.defineProperty(exports, "listMediaRefs", {
|
|
3421
|
+
enumerable: true,
|
|
3422
|
+
get: function() {
|
|
3423
|
+
return listMediaRefs;
|
|
3424
|
+
}
|
|
3425
|
+
});
|
|
3426
|
+
Object.defineProperty(exports, "mapMediaRefs", {
|
|
3427
|
+
enumerable: true,
|
|
3428
|
+
get: function() {
|
|
3429
|
+
return mapMediaRefs;
|
|
3430
|
+
}
|
|
3431
|
+
});
|
|
3178
3432
|
Object.defineProperty(exports, "readAiTextResult", {
|
|
3179
3433
|
enumerable: true,
|
|
3180
3434
|
get: function() {
|
|
3181
3435
|
return readAiTextResult;
|
|
3182
3436
|
}
|
|
3183
3437
|
});
|
|
3438
|
+
Object.defineProperty(exports, "transcriptOf", {
|
|
3439
|
+
enumerable: true,
|
|
3440
|
+
get: function() {
|
|
3441
|
+
return transcriptOf;
|
|
3442
|
+
}
|
|
3443
|
+
});
|
|
3184
3444
|
Object.defineProperty(exports, "validateDraft", {
|
|
3185
3445
|
enumerable: true,
|
|
3186
3446
|
get: function() {
|
|
@@ -3194,4 +3454,4 @@ Object.defineProperty(exports, "validateItemGroupDraft", {
|
|
|
3194
3454
|
}
|
|
3195
3455
|
});
|
|
3196
3456
|
|
|
3197
|
-
//# sourceMappingURL=ai-writing-
|
|
3457
|
+
//# sourceMappingURL=ai-writing-C4YRVrFi.cjs.map
|