@intellectif/lk-react 19.0.0 → 20.0.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 +39 -0
- package/README.md +1 -0
- package/dist/{WrittenResponse-CeHY5u3W.d.ts → WrittenResponse-D2YI8xOK.d.ts} +8 -2
- package/dist/{WrittenResponse-DT_Yqbq1.d.cts → WrittenResponse-obIDDYDH.d.cts} +8 -2
- package/dist/ai/useAiHelp.cjs +4 -4
- package/dist/ai/useAiHelp.d.cts +8 -2
- package/dist/ai/useAiHelp.d.ts +8 -2
- package/dist/ai/useAiHelp.js +2 -2
- package/dist/{chunk-SEQTGOOU.js → chunk-3XP6VO4G.js} +22 -15
- package/dist/chunk-3XP6VO4G.js.map +1 -0
- package/dist/{chunk-5QCZZBOX.js → chunk-5GI53C6T.js} +22 -13
- package/dist/chunk-5GI53C6T.js.map +1 -0
- package/dist/{chunk-XUQ4KRTT.cjs → chunk-6S2CKZ4H.cjs} +24 -17
- package/dist/chunk-6S2CKZ4H.cjs.map +1 -0
- package/dist/{chunk-XHF376ES.js → chunk-AG7MWZYZ.js} +25 -11
- package/dist/chunk-AG7MWZYZ.js.map +1 -0
- package/dist/{chunk-YIYMBCPV.js → chunk-AQIAQHJC.js} +18 -9
- package/dist/chunk-AQIAQHJC.js.map +1 -0
- package/dist/{chunk-4NERCBOW.js → chunk-BGA2IVQY.js} +7 -5
- package/dist/chunk-BGA2IVQY.js.map +1 -0
- package/dist/{chunk-JVWLXVYG.cjs → chunk-BJLQKJZX.cjs} +2 -2
- package/dist/chunk-BJLQKJZX.cjs.map +1 -0
- package/dist/{chunk-XAIJNGTU.cjs → chunk-CEUEXISZ.cjs} +18 -13
- package/dist/chunk-CEUEXISZ.cjs.map +1 -0
- package/dist/{chunk-WKA6FUR4.cjs → chunk-DZVF3IGB.cjs} +24 -18
- package/dist/chunk-DZVF3IGB.cjs.map +1 -0
- package/dist/{chunk-KOFA54C7.js → chunk-HI6BUUIJ.js} +18 -12
- package/dist/chunk-HI6BUUIJ.js.map +1 -0
- package/dist/{chunk-NGFTSRKO.cjs → chunk-HREA5SMB.cjs} +23 -14
- package/dist/chunk-HREA5SMB.cjs.map +1 -0
- package/dist/{chunk-NLTU5JFD.js → chunk-IQ55QNBI.js} +16 -11
- package/dist/chunk-IQ55QNBI.js.map +1 -0
- package/dist/chunk-IXMT7X3E.cjs +44 -0
- package/dist/chunk-IXMT7X3E.cjs.map +1 -0
- package/dist/{chunk-ZME5FMED.cjs → chunk-JLDSCHY5.cjs} +27 -13
- package/dist/chunk-JLDSCHY5.cjs.map +1 -0
- package/dist/{chunk-55W2472T.cjs → chunk-LK5VXNMR.cjs} +14 -14
- package/dist/{chunk-55W2472T.cjs.map → chunk-LK5VXNMR.cjs.map} +1 -1
- package/dist/{chunk-KGDGYRBL.cjs → chunk-NOC2M6LU.cjs} +7 -6
- package/dist/chunk-NOC2M6LU.cjs.map +1 -0
- package/dist/{chunk-UXV3V5NH.js → chunk-NWNQ2PHT.js} +23 -11
- package/dist/chunk-NWNQ2PHT.js.map +1 -0
- package/dist/chunk-ORBU3XKH.js +40 -0
- package/dist/chunk-ORBU3XKH.js.map +1 -0
- package/dist/{chunk-2X2NZF46.cjs → chunk-PWLJDP3X.cjs} +29 -20
- package/dist/chunk-PWLJDP3X.cjs.map +1 -0
- package/dist/{chunk-OWV7VRMQ.js → chunk-QKW2GGTV.js} +20 -11
- package/dist/chunk-QKW2GGTV.js.map +1 -0
- package/dist/{chunk-NZM3M5IR.cjs → chunk-RT2PP4SO.cjs} +27 -15
- package/dist/chunk-RT2PP4SO.cjs.map +1 -0
- package/dist/{chunk-QRT2W5N5.js → chunk-RUS42NMN.js} +5 -4
- package/dist/chunk-RUS42NMN.js.map +1 -0
- package/dist/{chunk-SXU26SFO.cjs → chunk-TRMWBJWT.cjs} +17 -8
- package/dist/chunk-TRMWBJWT.cjs.map +1 -0
- package/dist/{chunk-CQNKZ435.cjs → chunk-UHIOVOFK.cjs} +7 -5
- package/dist/chunk-UHIOVOFK.cjs.map +1 -0
- package/dist/{chunk-7NNMDZUU.js → chunk-W6DMHN33.js} +8 -8
- package/dist/{chunk-7NNMDZUU.js.map → chunk-W6DMHN33.js.map} +1 -1
- package/dist/{chunk-TEAVPUMX.js → chunk-WBLYD5QA.js} +2 -2
- package/dist/chunk-WBLYD5QA.js.map +1 -0
- package/dist/components/ActivityPreview.cjs +12 -12
- package/dist/components/ActivityPreview.d.cts +2 -2
- package/dist/components/ActivityPreview.d.ts +2 -2
- package/dist/components/ActivityPreview.js +11 -11
- package/dist/components/ActivitySequence.cjs +12 -12
- package/dist/components/ActivitySequence.d.cts +16 -4
- package/dist/components/ActivitySequence.d.ts +16 -4
- package/dist/components/ActivitySequence.js +11 -11
- package/dist/components/Dictation.cjs +6 -6
- package/dist/components/Dictation.d.cts +1 -1
- package/dist/components/Dictation.d.ts +1 -1
- package/dist/components/Dictation.js +5 -5
- package/dist/components/FillInTheBlanks.cjs +6 -6
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/FillInTheBlanks.js +5 -5
- package/dist/components/GapSelect.cjs +6 -6
- package/dist/components/GapSelect.d.cts +1 -1
- package/dist/components/GapSelect.d.ts +1 -1
- package/dist/components/GapSelect.js +5 -5
- package/dist/components/InteractiveVideo.cjs +12 -12
- package/dist/components/InteractiveVideo.d.cts +16 -3
- package/dist/components/InteractiveVideo.d.ts +16 -3
- package/dist/components/InteractiveVideo.js +10 -10
- package/dist/components/MultipleChoice.cjs +6 -6
- package/dist/components/MultipleChoice.d.cts +1 -1
- package/dist/components/MultipleChoice.d.ts +1 -1
- package/dist/components/MultipleChoice.js +5 -5
- package/dist/components/PronunciationFeedback.d.cts +1 -1
- package/dist/components/PronunciationFeedback.d.ts +1 -1
- package/dist/components/ReadAloud.cjs +4 -4
- package/dist/components/ReadAloud.d.cts +2 -2
- package/dist/components/ReadAloud.d.ts +2 -2
- package/dist/components/ReadAloud.js +3 -3
- package/dist/components/StimulusPanel.d.cts +1 -1
- package/dist/components/StimulusPanel.d.ts +1 -1
- package/dist/components/WrittenResponse.cjs +4 -3
- package/dist/components/WrittenResponse.d.cts +3 -3
- package/dist/components/WrittenResponse.d.ts +3 -3
- package/dist/components/WrittenResponse.js +3 -2
- package/dist/i18n/LkIntlProvider.d.cts +2 -2
- package/dist/i18n/LkIntlProvider.d.ts +2 -2
- package/dist/index.cjs +25 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +13 -13
- package/dist/index.js.map +1 -1
- package/dist/{types-DtPYG1hh.d.cts → types-B_PCcqO-.d.cts} +9 -1
- package/dist/{types-BY-4S5Rd.d.ts → types-C-GMCJ0g.d.ts} +9 -1
- package/package.json +3 -3
- package/dist/chunk-2X2NZF46.cjs.map +0 -1
- package/dist/chunk-4NERCBOW.js.map +0 -1
- package/dist/chunk-5QCZZBOX.js.map +0 -1
- package/dist/chunk-BRPEB43C.cjs +0 -13
- package/dist/chunk-BRPEB43C.cjs.map +0 -1
- package/dist/chunk-CQNKZ435.cjs.map +0 -1
- package/dist/chunk-JVWLXVYG.cjs.map +0 -1
- package/dist/chunk-KGDGYRBL.cjs.map +0 -1
- package/dist/chunk-KOFA54C7.js.map +0 -1
- package/dist/chunk-NGFTSRKO.cjs.map +0 -1
- package/dist/chunk-NLTU5JFD.js.map +0 -1
- package/dist/chunk-NZM3M5IR.cjs.map +0 -1
- package/dist/chunk-OWV7VRMQ.js.map +0 -1
- package/dist/chunk-QRT2W5N5.js.map +0 -1
- package/dist/chunk-RCMYEWBE.js +0 -11
- package/dist/chunk-RCMYEWBE.js.map +0 -1
- package/dist/chunk-SEQTGOOU.js.map +0 -1
- package/dist/chunk-SXU26SFO.cjs.map +0 -1
- package/dist/chunk-TEAVPUMX.js.map +0 -1
- package/dist/chunk-UXV3V5NH.js.map +0 -1
- package/dist/chunk-WKA6FUR4.cjs.map +0 -1
- package/dist/chunk-XAIJNGTU.cjs.map +0 -1
- package/dist/chunk-XHF376ES.js.map +0 -1
- package/dist/chunk-XUQ4KRTT.cjs.map +0 -1
- package/dist/chunk-YIYMBCPV.js.map +0 -1
- package/dist/chunk-ZME5FMED.cjs.map +0 -1
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
var chunkLX2X4KC7_cjs = require('./chunk-LX2X4KC7.cjs');
|
|
5
5
|
var chunkUDCQTOVU_cjs = require('./chunk-UDCQTOVU.cjs');
|
|
6
6
|
var chunkOD65OZIW_cjs = require('./chunk-OD65OZIW.cjs');
|
|
7
|
-
var
|
|
8
|
-
var
|
|
7
|
+
var chunkIXMT7X3E_cjs = require('./chunk-IXMT7X3E.cjs');
|
|
8
|
+
var chunkBJLQKJZX_cjs = require('./chunk-BJLQKJZX.cjs');
|
|
9
9
|
var chunkAPZ6JFN6_cjs = require('./chunk-APZ6JFN6.cjs');
|
|
10
10
|
var chunkEANGKAKG_cjs = require('./chunk-EANGKAKG.cjs');
|
|
11
11
|
var chunkL7WY5YFQ_cjs = require('./chunk-L7WY5YFQ.cjs');
|
|
@@ -208,12 +208,14 @@ function ReadAloud({
|
|
|
208
208
|
assessment,
|
|
209
209
|
breakThreshold,
|
|
210
210
|
monotoneThreshold,
|
|
211
|
-
workletUrl
|
|
211
|
+
workletUrl,
|
|
212
|
+
delivery
|
|
212
213
|
}) {
|
|
213
214
|
const isExam = renderMode === "exam";
|
|
215
|
+
const policy = chunkIXMT7X3E_cjs.useDeliveryPolicy(delivery);
|
|
214
216
|
const isReview = renderMode === "review";
|
|
215
217
|
const s = chunkAHGB5HH6_cjs.useLkStrings(strings);
|
|
216
|
-
const slot = react.useContext(
|
|
218
|
+
const slot = react.useContext(chunkBJLQKJZX_cjs.SequenceSlotContext);
|
|
217
219
|
const captureGroup = slot?.captureGroup;
|
|
218
220
|
const devError = react.useMemo(() => {
|
|
219
221
|
if (!chunkEANGKAKG_cjs.isDevelopment()) {
|
|
@@ -478,7 +480,7 @@ function ReadAloud({
|
|
|
478
480
|
return;
|
|
479
481
|
}
|
|
480
482
|
onComplete?.(
|
|
481
|
-
|
|
483
|
+
chunkBJLQKJZX_cjs.stampTake(
|
|
482
484
|
{
|
|
483
485
|
score: grade.score,
|
|
484
486
|
maxScore: grade.maxScore,
|
|
@@ -517,7 +519,7 @@ function ReadAloud({
|
|
|
517
519
|
submittingRef.current = true;
|
|
518
520
|
const takes = recorder.takesUsed;
|
|
519
521
|
const run = runRef.current;
|
|
520
|
-
const take2 =
|
|
522
|
+
const take2 = chunkBJLQKJZX_cjs.mintTake();
|
|
521
523
|
beginTakeReport(take2);
|
|
522
524
|
try {
|
|
523
525
|
let ref = null;
|
|
@@ -543,7 +545,7 @@ function ReadAloud({
|
|
|
543
545
|
setUploaded(ref);
|
|
544
546
|
fire("recording-uploaded", { takes, durationMs: current.durationMs });
|
|
545
547
|
}
|
|
546
|
-
onSubmit?.(
|
|
548
|
+
onSubmit?.(chunkBJLQKJZX_cjs.stampTake(responseOf(ref, takes), take2));
|
|
547
549
|
fire("submitted", { takes, durationMs: current?.durationMs ?? 0 });
|
|
548
550
|
if (isExam || ref === null) {
|
|
549
551
|
complete();
|
|
@@ -595,10 +597,10 @@ function ReadAloud({
|
|
|
595
597
|
const keptEvidence = react.useMemo(() => chunkLX2X4KC7_cjs.readEvidence(assessment), [assessment]);
|
|
596
598
|
const storedGrade = outcomeReading?.kind === "graded" && outcomeReading.status === "graded" ? outcomeReading.grade : null;
|
|
597
599
|
const storedMarks = react.useMemo(
|
|
598
|
-
() => isReview && keptEvidence === null && storedGrade?.details !== void 0 ? marksFromDetails(storedGrade.details) : null,
|
|
599
|
-
[isReview, keptEvidence, storedGrade]
|
|
600
|
+
() => policy.feedback && isReview && keptEvidence === null && storedGrade?.details !== void 0 ? marksFromDetails(storedGrade.details) : null,
|
|
601
|
+
[policy.feedback, isReview, keptEvidence, storedGrade]
|
|
600
602
|
);
|
|
601
|
-
const feedbackAssessment = isReview ? keptEvidence : phase.kind === "graded" ? phase.assessment : null;
|
|
603
|
+
const feedbackAssessment = !policy.feedback ? null : isReview ? keptEvidence : phase.kind === "graded" ? phase.assessment : null;
|
|
602
604
|
const feedbackGrade = isReview ? storedGrade : phase.kind === "graded" ? phase.grade : null;
|
|
603
605
|
const feedbackAudio = playback.refused ? null : takeAudio;
|
|
604
606
|
const rootRef = react.useRef(null);
|
|
@@ -633,7 +635,13 @@ function ReadAloud({
|
|
|
633
635
|
switch (phase.kind) {
|
|
634
636
|
case "graded": {
|
|
635
637
|
const grade = phase.grade;
|
|
636
|
-
|
|
638
|
+
if (grade === null) {
|
|
639
|
+
return { text: `${s.answerSubmitted} ${s.couldNotBeGraded}`, feedback: null };
|
|
640
|
+
}
|
|
641
|
+
if (!policy.feedback) {
|
|
642
|
+
return { text: s.answerSubmitted, feedback: null };
|
|
643
|
+
}
|
|
644
|
+
return {
|
|
637
645
|
text: `${s.answerSubmitted} ${s.scoreAnnouncement(chunkLX2X4KC7_cjs.percentOfGrade(grade), grade.passed)}`,
|
|
638
646
|
feedback: grade.feedback
|
|
639
647
|
};
|
|
@@ -647,7 +655,7 @@ function ReadAloud({
|
|
|
647
655
|
default:
|
|
648
656
|
return null;
|
|
649
657
|
}
|
|
650
|
-
}, [isReview, isExam, phase, submitted, s, recorder.status, take, maxSeconds]);
|
|
658
|
+
}, [isReview, isExam, phase, submitted, s, recorder.status, take, maxSeconds, policy.feedback]);
|
|
651
659
|
const alertText = recorder.error !== null ? s.readAloudRecorderError(recorder.error) : phase.kind === "upload-failed" ? s.readAloudUploadFailed : phase.kind === "assess-failed" ? s.readAloudAssessmentFailed : null;
|
|
652
660
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
653
661
|
"form",
|
|
@@ -856,7 +864,11 @@ function ReadAloud({
|
|
|
856
864
|
}) }),
|
|
857
865
|
/* @__PURE__ */ jsxRuntime.jsx("ul", { className: "lk-ra-legend", children: STORED_STATES.map((state2) => /* @__PURE__ */ jsxRuntime.jsx("li", { "data-state": state2, children: s.pronunciationLegend(state2) }, state2)) })
|
|
858
866
|
] }) : null,
|
|
859
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
867
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkIXMT7X3E_cjs.FeedbackRegion, { id: `${data.id}-feedback`, children: isReview ? (
|
|
868
|
+
// A grade read back is feedback; "not graded yet" and a grade that
|
|
869
|
+
// could not be read are not.
|
|
870
|
+
outcomeReading !== void 0 && (policy.feedback || outcomeReading.kind !== "graded") ? /* @__PURE__ */ jsxRuntime.jsx(OutcomeSummary, { reading: outcomeReading, s, scoreShown: gradeShownInPanel }) : null
|
|
871
|
+
) : gradeShownInPanel ? (
|
|
860
872
|
// Announced, not repeated. A grade the feedback panel is already
|
|
861
873
|
// showing would otherwise be read on screen twice, feedback and all —
|
|
862
874
|
// but a live region is the only thing that TELLS a screen-reader
|
|
@@ -875,5 +887,5 @@ function ReadAloud2(props) {
|
|
|
875
887
|
|
|
876
888
|
exports.ReadAloud = ReadAloud2;
|
|
877
889
|
exports.stopCaptureGroup = stopCaptureGroup;
|
|
878
|
-
//# sourceMappingURL=chunk-
|
|
879
|
-
//# sourceMappingURL=chunk-
|
|
890
|
+
//# sourceMappingURL=chunk-RT2PP4SO.cjs.map
|
|
891
|
+
//# sourceMappingURL=chunk-RT2PP4SO.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/shared/capture-registry.ts","../src/components/ReadAloud/recording-bounds.ts","../src/components/ReadAloud/ReadAloud.tsx","../src/components/ReadAloud/index.tsx"],"names":["READ_ALOUD_MAX_SECONDS","READ_ALOUD_MAX_TAKES","jsxs","Fragment","jsx","percentOfGrade","useDeliveryPolicy","useLkStrings","useContext","SequenceSlotContext","useMemo","isDevelopment","assertRedacted","validateActivity","ActivitySchemaError","useSpeechRecorder","useActivityState","useState","useId","useRef","useEffect","take","useCallback","usePlaybackRefusal","readAssessResult","stampTake","xAPIBuilder","ANONYMOUS_ACTOR","objectIdFor","xapiDefinitionFor","fractionOfGrade","mintTake","localeDirectionOf","readOutcome","readEvidence","ActivityMedia","PronunciationFeedback","markSentence","VISUALLY_HIDDEN","state","FeedbackRegion","ReadAloud","ActivityErrorBoundary"],"mappings":";;;;;;;;;;;;;;;;;AA0BA,IAAM,MAAA,uBAAa,GAAA,EAA6B;AAGzC,SAAS,gBAAA,CAAiB,OAAe,IAAA,EAA8B;AAC5E,EAAA,IAAI,OAAA,GAAU,MAAA,CAAO,GAAA,CAAI,KAAK,CAAA;AAC9B,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA,OAAA,uBAAc,GAAA,EAAI;AAClB,IAAA,MAAA,CAAO,GAAA,CAAI,OAAO,OAAO,CAAA;AAAA,EAC3B;AACA,EAAA,OAAA,CAAQ,IAAI,IAAI,CAAA;AAChB,EAAA,OAAO,MAAM;AACX,IAAA,MAAM,OAAA,GAAU,MAAA,CAAO,GAAA,CAAI,KAAK,CAAA;AAChC,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA;AAAA,IACF;AACA,IAAA,OAAA,CAAQ,OAAO,IAAI,CAAA;AACnB,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,MAAA,MAAA,CAAO,OAAO,KAAK,CAAA;AAAA,IACrB;AAAA,EACF,CAAA;AACF;AASO,SAAS,iBAAiB,KAAA,EAAqB;AACpD,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,GAAA,CAAI,KAAK,CAAA;AAChC,EAAA,IAAI,YAAY,MAAA,EAAW;AACzB,IAAA;AAAA,EACF;AACA,EAAA,KAAA,MAAW,IAAA,IAAQ,CAAC,GAAG,OAAO,CAAA,EAAG;AAC/B,IAAA,IAAA,EAAK;AAAA,EACP;AACF;ACjDA,IAAM,QAAA,GAAW,CAAC,KAAA,KAChB,OAAO,UAAU,QAAA,IAAY,MAAA,CAAO,SAAS,KAAK,CAAA;AA0B7C,SAAS,oBAAoB,SAAA,EAAqC;AACvE,EAAA,MAAM,SACJ,OAAO,SAAA,KAAc,YAAY,SAAA,KAAc,IAAA,GAC1C,YACD,EAAC;AACP,EAAA,MAAM,UAAA,GACJ,QAAA,CAAS,MAAA,CAAO,UAAU,CAAA,IAAK,MAAA,CAAO,UAAA,GAAa,CAAA,GAC/C,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,UAAA,EAAYA,6BAAsB,CAAA,GAClDA,6BAAA;AACN,EAAA,MAAM,UAAA,GACJ,QAAA,CAAS,MAAA,CAAO,UAAU,CAAA,IAAK,MAAA,CAAO,UAAA,IAAc,CAAA,IAAK,MAAA,CAAO,UAAA,GAAa,UAAA,GACzE,MAAA,CAAO,UAAA,GACP,MAAA;AACN,EAAA,MAAM,QAAQ,MAAA,CAAO,QAAA;AACrB,EAAA,MAAM,QAAA,GACJ,UAAU,MAAA,IAAa,KAAA,KAAU,OAC7B,MAAA,GACA,QAAA,CAAS,KAAK,CAAA,GACZ,IAAA,CAAK,IAAI,IAAA,CAAK,GAAA,CAAI,GAAG,IAAA,CAAK,KAAA,CAAM,KAAK,CAAC,CAAA,EAAGC,2BAAoB,CAAA,GAC7DA,2BAAA;AACR,EAAA,OAAO,EAAE,UAAA,EAAY,UAAA,EAAY,QAAA,EAAS;AAC5C;AASO,SAAS,aAAA,CACd,YAAA,EACA,UAAA,EACA,QAAA,EACQ;AACR,EAAA,MAAM,UAAU,YAAA,GAAe,GAAA;AAC/B,EAAA,MAAM,KAAA,GAAQ,aAAa,SAAA,GAAY,IAAA,CAAK,MAAM,OAAO,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA;AAC/E,EAAA,OAAO,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,EAAG,KAAK,GAAG,UAAU,CAAA;AAChD;ACXA,IAAI,gBAAA,GAAmB,CAAA;AAGvB,IAAM,eAAA,GAAkB,iEAAA;AAGxB,IAAM,UAAA,GAAa,QAAA;AAGnB,IAAM,aAAA,GAA4D;AAAA,EAChE,SAAA;AAAA,EACA,eAAA;AAAA,EACA;AACF,CAAA;AAGA,IAAM,WAAA,GAAc,EAAA;AAmHpB,SAAS,WAAA,CAAY,OAAoB,QAAA,EAAwC;AAC/E,EAAA,OACE,KAAA,CAAM,SAAS,eAAA,IACd,KAAA,CAAM,SAAS,eAAA,IAAmB,KAAA,CAAM,aAAa,QAAA,KAAa,IAAA;AAEvE;AAQA,SAAS,WAAA,CAAY,OAAoB,QAAA,EAA0C;AACjF,EAAA,IAAI,KAAA,CAAM,IAAA,KAAS,WAAA,IAAe,KAAA,CAAM,SAAS,WAAA,EAAa;AAC5D,IAAA,OAAO,WAAA;AAAA,EACT;AACA,EAAA,OAAO,WAAA,CAAY,KAAA,EAAO,QAAQ,CAAA,GAAI,WAAA,GAAc,SAAA;AACtD;AASA,SAAS,UAAU,MAAA,EAAuC;AACxD,EAAA,OAAO,MAAA,KAAW,eAAe,MAAA,KAAW,uBAAA;AAC9C;AAmBA,SAAS,YAAA,CACP,MACA,MAAA,EACQ;AACR,EAAA,MAAM,OAAO,CAAC,KAAA,KAAmC,OAAO,KAAA,KAAU,WAAW,KAAA,GAAQ,IAAA;AACrF,EAAA,OAAO,KAAK,SAAA,CAAU;AAAA,IACpB,IAAA,CAAK,KAAK,EAAE,CAAA;AAAA,IACZ,IAAA,CAAK,KAAK,aAAa,CAAA;AAAA,IACvB,IAAA,CAAK,KAAK,MAAM,CAAA;AAAA,IAChB,MAAA,CAAO,OAAO,UAAU,CAAA;AAAA,IACxB,MAAA,CAAO,OAAO,UAAU,CAAA;AAAA,IACxB,MAAA,CAAO,OAAO,QAAQ;AAAA,GACvB,CAAA;AACH;AASA,SAAS,YAAA,CACP,MACA,KAAA,EAC+B;AAC/B,EAAA,MAAM,OAAO,IAAA,CAAK,SAAA;AAClB,EAAA,IAAI,SAAS,MAAA,EAAW;AACtB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,MAAM,MAAA,GAA8B,IAAA,CAAK,KAAA,EAAO,QAAA,IAAY,EAAC;AAC7D,EAAA,MAAM,EAAE,QAAA,EAAU,OAAA,EAAS,GAAG,WAAU,GAAI,MAAA;AAC5C,EAAA,MAAM,SAAA,GAAY,OAAO,MAAA,CAAO,SAAS,EAAE,IAAA,CAAK,CAAC,KAAA,KAAU,KAAA,KAAU,MAAS,CAAA;AAC9E,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,OAAA;AAAA,IACN,KAAK,IAAA,CAAK,GAAA;AAAA,IACV,GAAA,EAAK,KAAK,GAAA,IAAO,KAAA;AAAA,IACjB,GAAI,SAAA,GAAY,EAAE,QAAA,EAAU,SAAA,KAAc;AAAC,GAC7C;AACF;AAYA,SAAS,iBAAiB,OAAA,EAAoE;AAC5F,EAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,IAAK,CAAC,OAAA,CAAQ,KAAA,CAAM,CAAC,MAAA,KAAW,UAAA,CAAW,IAAA,CAAK,MAAA,CAAO,MAAM,CAAC,CAAA,EAAG;AACtF,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,KAAW;AAC7B,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,eAAe,CAAA,GAC9C,MAAA,CAAO,eAAA,CAAgB,IAAA,CAAK,GAAG,CAAA,GAC/B,MAAA,CAAO,eAAA;AACX,IAAA,MAAM,SAAA,GAAY,KAAA,CAAM,OAAA,CAAQ,MAAA,CAAO,eAAe,CAAA,GAClD,MAAA,CAAO,eAAA,CAAgB,IAAA,CAAK,GAAG,CAAA,GAC/B,MAAA,CAAO,eAAA;AACX,IAAA,MAAM,UAAU,MAAA,CAAO,OAAA,KAAY,SAAY,MAAA,CAAO,OAAA,KAAY,YAAY,MAAA,CAAO,OAAA;AACrF,IAAA,MAAM,QAAyC,OAAA,GAC3C,SAAA,GACA,MAAA,CAAO,OAAA,KAAY,uBACjB,SAAA,GACA,eAAA;AACN,IAAA,OAAO;AAAA,MACL,QAAQ,MAAA,CAAO,MAAA;AAAA,MACf,SAAA;AAAA,MACA,KAAA;AAAA,MACA,KAAA;AAAA;AAAA;AAAA,MAGA,GAAI,OAAO,MAAA,CAAO,KAAA,KAAU,QAAA,GAAW,EAAE,QAAA,EAAU,MAAA,CAAO,KAAA,GAAQ,GAAA,EAAI,GAAI;AAAC,KAC7E;AAAA,EACF,CAAC,CAAA;AACH;AAGA,SAAS,YAAY,QAAA,EAA4D;AAC/E,EAAA,OAAO,aAAa,MAAA,IAAa,QAAA,CAAS,IAAA,KAAS,YAAA,GAAe,SAAS,SAAA,GAAY,IAAA;AACzF;AAGA,SAAS,UAAA,CAAW,WAAgC,KAAA,EAAyC;AAC3F,EAAA,OAAO,EAAE,IAAA,EAAM,YAAA,EAAc,SAAA,EAAW,GAAI,KAAA,GAAQ,CAAA,GAAI,EAAE,KAAA,EAAM,GAAI,EAAC,EAAG;AAC1E;AASA,SAAS,iBAAA,CAAkB,MAA0B,CAAA,EAAsB;AACzE,EAAA,OAAO,SAAS,WAAA,IAAe,IAAA,KAAS,0BAAA,GACpC,CAAA,CAAE,oBACF,CAAA,CAAE,oBAAA;AACR;AAQA,SAAS,gBAAA,CAAiB,EAAE,YAAA,EAAa,EAA0C;AACjF,EAAA,IAAI,iBAAiB,IAAA,EAAM;AACzB,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,uBACEC,eAAA,CAAAC,mBAAA,EAAA,EACG,QAAA,EAAA;AAAA,IAAA,YAAA,CAAa,IAAA;AAAA,IACb,YAAA,CAAa,2BACZD,eAAA,CAAAC,mBAAA,EAAA,EACG,QAAA,EAAA;AAAA,MAAA,GAAA;AAAA,sBACDC,cAAA,CAAC,MAAA,EAAA,EAAK,GAAA,EAAI,MAAA,EAAQ,uBAAa,QAAA,EAAS;AAAA,KAAA,EAC1C,CAAA,GACE;AAAA,GAAA,EACN,CAAA;AAEJ;AAWA,SAAS,cAAA,CAAe;AAAA,EACtB,OAAA;AAAA,EACA,CAAA;AAAA,EACA;AACF,CAAA,EAIG;AACD,EAAA,QAAQ,QAAQ,IAAA;AAAM,IACpB,KAAK,QAAA;AACH,MAAA,uBACEA,cAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAU,eAAA;AAAA,UACV,eAAa,OAAA,CAAQ,MAAA;AAAA,UACrB,aAAA,EAAa,MAAA,CAAO,OAAA,CAAQ,KAAA,CAAM,MAAM,CAAA;AAAA,UAEvC,QAAA,EAAA,UAAA,GAAa,uBACZF,eAAA,CAAAC,mBAAA,EAAA,EACE,QAAA,EAAA;AAAA,4BAAAC,cAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,aAAA,EACV,QAAA,EAAA,CAAA,CAAE,iBAAA,CAAkBC,gCAAA,CAAe,OAAA,CAAQ,KAAK,CAAA,EAAG,OAAA,CAAQ,KAAA,CAAM,MAAM,CAAA,EAC1E,CAAA;AAAA,YACC,OAAA,CAAQ,KAAA,CAAM,QAAA,mBACbD,cAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,sBAAA,EAAuB,GAAA,EAAI,MAAA,EACrC,QAAA,EAAA,OAAA,CAAQ,KAAA,CAAM,QAAA,EACjB,CAAA,GACE;AAAA,WAAA,EACN;AAAA;AAAA,OAEJ;AAAA,IAEJ,KAAK,UAAA;AACH,MAAA,uBACEA,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,eAAA,EAAgB,aAAA,EAAY,UAAA,EACzC,QAAA,kBAAAA,cAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,aAAA,EAAe,QAAA,EAAA,CAAA,CAAE,aAAA,EAAc,CAAA,EAC9C,CAAA;AAAA,IAEJ,KAAK,YAAA;AAGH,MAAA,uBACEA,cAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAU,eAAA;AAAA,UACV,aAAA,EAAY,YAAA;AAAA,UACX,GAAI,QAAQ,IAAA,KAAS,MAAA,GAAY,EAAE,WAAA,EAAa,OAAA,CAAQ,IAAA,EAAK,GAAI,EAAC;AAAA,UAEnE,QAAA,kBAAAA,cAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,aAAA,EAAe,YAAE,gBAAA,EAAiB;AAAA;AAAA,OACjD;AAAA,IAEJ;AAKE,MAAA,uBACEA,cAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAU,eAAA;AAAA,UACT,GAAI,QAAQ,MAAA,KAAW,MAAA,GAAY,EAAE,aAAA,EAAe,OAAA,CAAQ,MAAA,EAAO,GAAI,EAAC;AAAA,UAEzE,QAAA,kBAAAA,cAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,aAAA,EAAe,YAAE,gBAAA,EAAiB;AAAA;AAAA,OACjD;AAAA;AAGR;AAsCO,SAAS,SAAA,CAAU;AAAA,EACxB,IAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA;AAAA,EACA,aAAA;AAAA,EACA,KAAA;AAAA,EACA,YAAA;AAAA,EACA,gBAAA;AAAA,EACA,UAAA,GAAa,UAAA;AAAA,EACb,OAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,gBAAA;AAAA,EACA,UAAA;AAAA,EACA,cAAA;AAAA,EACA,iBAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF,CAAA,EAAmB;AACjB,EAAA,MAAM,SAAS,UAAA,KAAe,MAAA;AAI9B,EAAA,MAAM,MAAA,GAASE,oCAAkB,QAAQ,CAAA;AACzC,EAAA,MAAM,WAAW,UAAA,KAAe,QAAA;AAChC,EAAA,MAAM,CAAA,GAAIC,+BAAa,OAAO,CAAA;AAO9B,EAAA,MAAM,IAAA,GAAOC,iBAAWC,qCAAmB,CAAA;AAC3C,EAAA,MAAM,eAAe,IAAA,EAAM,YAAA;AAE3B,EAAA,MAAM,QAAA,GAAWC,cAAQ,MAAM;AAC7B,IAAA,IAAI,CAACC,iCAAc,EAAG;AACpB,MAAA,OAAO,IAAA;AAAA,IACT;AAIA,IAAA,IAAI,IAAA,CAAK,aAAa,IAAA,EAAM;AAC1B,MAAA,IAAI;AACF,QAAAC,qBAAA,CAAe,IAAI,CAAA;AACnB,QAAA,OAAO,IAAA;AAAA,MACT,SAAS,KAAA,EAAO;AACd,QAAA,OAAO,iBAAiB,KAAA,GAAQ,KAAA,GAAQ,IAAI,KAAA,CAAM,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,MACjE;AAAA,IACF;AACA,IAAA,MAAM,MAAA,GAASC,uBAAA,CAAiB,YAAA,EAAc,IAAI,CAAA;AAClD,IAAA,OAAO,OAAO,OAAA,GAAU,IAAA,GAAO,IAAIC,0BAAA,CAAoB,YAAA,EAAc,OAAO,MAAM,CAAA;AAAA,EACpF,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAOT,EAAA,MAAM,EAAE,UAAA,EAAY,UAAA,EAAY,UAAS,GAAI,mBAAA,CAAoB,KAAK,SAAS,CAAA;AAC/E,EAAA,MAAM,aAAa,YAAA,CAAa,IAAA,EAAM,EAAE,UAAA,EAAY,UAAA,EAAY,UAAU,CAAA;AAE1E,EAAA,MAAM,QAAA,GAAWC,mCAAA;AAAA,IACfL,aAAA;AAAA,MACE,OAAO;AAAA,QACL,eAAe,UAAA,GAAa,GAAA;AAAA,QAC5B,GAAI,eAAe,MAAA,GAAY,EAAE,eAAe,UAAA,GAAa,GAAA,KAAS,EAAC;AAAA,QACvE,GAAI,QAAA,KAAa,MAAA,GAAY,EAAE,QAAA,KAAa,EAAC;AAAA;AAAA;AAAA,QAG7C,GAAI,UAAA,KAAe,MAAA,GAAY,EAAE,UAAA,KAAe;AAAC,OACnD,CAAA;AAAA,MACA,CAAC,UAAA,EAAY,UAAA,EAAY,QAAA,EAAU,UAAU;AAAA;AAC/C,GACF;AAEA,EAAA,MAAM,EAAE,KAAA,EAAO,KAAA,EAAO,QAAA,EAAU,YAAA,EAAc,OAAM,GAAIM,kCAAA;AAAA,IACtD,gBAAA,KAAqB,OAAO,WAAA,GAAc;AAAA,GAC5C;AACA,EAAA,MAAM,CAAC,OAAO,QAAQ,CAAA,GAAIC,eAAsB,EAAE,IAAA,EAAM,QAAQ,CAAA;AAChE,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,eAA8B,IAAI,CAAA;AAClE,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,eAAwB,IAAI,CAAA;AAC1D,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAIA,eAAwB,IAAI,CAAA;AAGlE,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIA,eAAS,CAAC,CAAA;AAE9C,EAAA,MAAM,MAAMC,WAAA,EAAM;AAClB,EAAA,MAAM,OAAA,GAAU,GAAG,GAAG,CAAA,MAAA,CAAA;AAGtB,EAAA,MAAM,CAAC,WAAW,CAAA,GAAID,cAAA,CAAS,MAAM;AACnC,IAAA,gBAAA,IAAoB,CAAA;AACpB,IAAA,OAAO,gBAAgB,gBAAgB,CAAA,CAAA;AAAA,EACzC,CAAC,CAAA;AAID,EAAA,MAAM,cAAA,GAAiBE,aAAO,aAAa,CAAA;AAC3C,EAAA,cAAA,CAAe,OAAA,GAAU,aAAA;AACzB,EAAA,MAAM,UAAA,GAAaA,aAAO,gBAAgB,CAAA;AAC1C,EAAA,UAAA,CAAW,OAAA,GAAU,gBAAA;AAGrB,EAAA,MAAM,aAAA,GAAgBA,aAAO,KAAK,CAAA;AASlC,EAAA,MAAM,MAAA,GAASA,aAAO,CAAC,CAAA;AAIvB,EAAA,MAAM,OAAA,GAAUA,aAAO,IAAI,CAAA;AAC3B,EAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAClB,EAAA,MAAM,eAAA,GAAkBA,aAAsB,IAAI,CAAA;AAClD,EAAAC,eAAA;AAAA,IACE,MAAM,MAAM;AACV,MAAA,MAAA,CAAO,OAAA,IAAW,CAAA;AAClB,MAAA,MAAMC,QAAO,eAAA,CAAgB,OAAA;AAC7B,MAAA,IAAIA,UAAS,IAAA,EAAM;AACjB,QAAA,OAAA,CAAQ,OAAA,EAAS,SAAA,CAAUA,KAAAA,EAAM,SAAS,CAAA;AAAA,MAC5C;AAAA,IACF,CAAA;AAAA,IACA;AAAC,GACH;AAEA,EAAA,MAAM,IAAA,GAAOC,iBAAA;AAAA,IACX,CAAC,MAAuB,OAAA,KAA2C;AACjE,MAAA,cAAA,CAAe,OAAA,GAAU,EAAE,IAAA,EAAM,UAAA,EAAY,IAAA,CAAK,EAAA,EAAI,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,EAAG,OAAA,EAAS,CAAA;AAAA,IACxF,CAAA;AAAA,IACA,CAAC,KAAK,EAAE;AAAA,GACV;AAMA,EAAA,MAAM,aAAA,GAAgBH,YAAA,CAA6B,QAAA,CAAS,MAAM,CAAA;AAClE,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,aAAA,CAAc,OAAA,KAAY,QAAA,CAAS,MAAA,EAAQ;AAC7C,MAAA;AAAA,IACF;AACA,IAAA,aAAA,CAAc,UAAU,QAAA,CAAS,MAAA;AACjC,IAAA,IAAI,QAAA,CAAS,WAAW,WAAA,EAAa;AACnC,MAAA,IAAA,CAAK,qBAAqB,EAAE,KAAA,EAAO,SAAS,SAAA,EAAW,UAAA,EAAY,GAAG,CAAA;AAAA,IACxE,WAAW,QAAA,CAAS,MAAA,KAAW,UAAA,IAAc,QAAA,CAAS,SAAS,IAAA,EAAM;AACnE,MAAA,IAAA,CAAK,mBAAA,EAAqB;AAAA,QACxB,OAAO,QAAA,CAAS,SAAA;AAAA,QAChB,UAAA,EAAY,SAAS,IAAA,CAAK;AAAA,OAC3B,CAAA;AAAA,IACH;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,CAAS,MAAA,EAAQ,SAAS,IAAA,EAAM,QAAA,CAAS,SAAA,EAAW,IAAI,CAAC,CAAA;AAE7D,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,QAAA,CAAS,UAAU,IAAA,EAAM;AAC3B,MAAA,aAAA,CAAc,CAAC,KAAA,KAAU,KAAA,GAAQ,CAAC,CAAA;AAAA,IACpC;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,CAAS,KAAK,CAAC,CAAA;AAInB,EAAA,MAAM,OAAO,QAAA,CAAS,IAAA;AACtB,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,IAAA,KAAS,IAAA,IAAQ,OAAO,GAAA,CAAI,oBAAoB,UAAA,EAAY;AAC9D,MAAA;AAAA,IACF;AACA,IAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA;AACzC,IAAA,UAAA,CAAW,GAAG,CAAA;AACd,IAAA,OAAO,MAAM;AACX,MAAA,GAAA,CAAI,gBAAgB,GAAG,CAAA;AACvB,MAAA,UAAA,CAAW,IAAI,CAAA;AAAA,IACjB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,KAAA,IAAS,YAAY,CAAA;AAClD,EAAA,MAAM,WAAA,GAAc,UAAU,GAAA,IAAO,IAAA;AAKrC,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,UAAU,UAAA,CAAW,OAAA;AAC3B,IAAA,IAAI,CAAC,QAAA,IAAY,QAAA,KAAa,IAAA,IAAQ,OAAA,EAAS,gBAAgB,MAAA,EAAW;AACxE,MAAA;AAAA,IACF;AACA,IAAA,IAAI,IAAA,GAAO,IAAA;AACX,IAAA,KAAK,QAAQ,OAAA,CAAQ,OAAA,CAAQ,WAAA,CAAY,QAAQ,CAAC,CAAA,CAAE,IAAA;AAAA,MAClD,CAAC,GAAA,KAAQ;AACP,QAAA,IAAI,IAAA,EAAM;AACR,UAAA,cAAA,CAAe,GAAG,CAAA;AAAA,QACpB;AAAA,MACF,CAAA;AAAA,MACA,MAAM;AAAA,MAEN;AAAA,KACF;AACA,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,GAAO,KAAA;AAAA,IACT,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,EAAU,WAAA,EAAa,UAAU,CAAC,CAAA;AAetC,EAAA,MAAM,SAAA,GAAYD,YAAA,CAAO,QAAA,CAAS,MAAM,CAAA;AACxC,EAAA,SAAA,CAAU,UAAU,QAAA,CAAS,MAAA;AAC7B,EAAA,MAAM,iBAAA,GAAoBG,kBAAY,MAAM;AAC1C,IAAA,IAAI,SAAA,CAAU,SAAA,CAAU,OAAO,CAAA,EAAG;AAChC,MAAA,QAAA,CAAS,OAAA,EAAQ;AAAA,IACnB;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,CAAS,OAAO,CAAC,CAAA;AAIrB,EAAAF,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,iBAAiB,MAAA,EAAW;AAC9B,MAAA;AAAA,IACF;AACA,IAAA,OAAO,gBAAA,CAAiB,cAAc,iBAAiB,CAAA;AAAA,EACzD,CAAA,EAAG,CAAC,YAAA,EAAc,iBAAiB,CAAC,CAAA;AAIpC,EAAA,MAAM,mBAAA,GAAsBD,aAAO,gBAAgB,CAAA;AACnD,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,mBAAA,CAAoB,OAAA,GAAU,gBAAA;AAAA,EAChC,CAAA,EAAG,CAAC,gBAAgB,CAAC,CAAA;AAIrB,EAAA,MAAM,cAAA,GAAiBD,aAAO,UAAU,CAAA;AACxC,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,cAAA,CAAe,YAAY,UAAA,EAAY;AACzC,MAAA;AAAA,IACF;AACA,IAAA,cAAA,CAAe,OAAA,GAAU,UAAA;AACzB,IAAA,aAAA,CAAc,OAAA,GAAU,KAAA;AAExB,IAAA,MAAA,CAAO,OAAA,IAAW,CAAA;AAClB,IAAA,QAAA,CAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,CAAA;AACzB,IAAA,WAAA,CAAY,IAAI,CAAA;AAChB,IAAA,cAAA,CAAe,IAAI,CAAA;AAOnB,IAAA,QAAA,CAAS,KAAA,EAAM;AACf,IAAA,KAAA,CAAM,mBAAA,CAAoB,OAAA,KAAY,IAAA,GAAO,WAAA,GAAc,MAAM,CAAA;AAAA,EACnE,GAAG,CAAC,UAAA,EAAY,KAAA,EAAO,QAAA,CAAS,KAAK,CAAC,CAAA;AAatC,EAAA,MAAM,SAAA,GAAY,KAAA,CAAM,IAAA,KAAS,MAAA,GAAS,OAAO,KAAA,CAAM,IAAA;AACvD,EAAA,MAAM,cAAA,GAAiB,WAAA,CAAY,KAAA,EAAO,QAAQ,CAAA;AAClD,EAAAA,eAAA,CAAU,MAAM;AAGd,IAAA,MAAMC,KAAAA,GAAO,aAAa,eAAA,CAAgB,OAAA;AAC1C,IAAA,IAAI,IAAA,KAAS,IAAA,IAAQA,KAAAA,KAAS,IAAA,EAAM;AAClC,MAAA;AAAA,IACF;AACA,IAAA,eAAA,CAAgB,OAAA,GAAUA,KAAAA;AAC1B,IAAA,IAAA,CAAK,SAAA,CAAUA,OAAM,cAAc,CAAA;AAAA,EACrC,CAAA,EAAG,CAAC,IAAA,EAAM,SAAA,EAAW,cAAc,CAAC,CAAA;AAapC,EAAA,MAAM,SAAA,GAAYF,aAAuB,IAAI,CAAA;AAC7C,EAAA,MAAM,WAAA,GAAc,SAAA,CAAU,QAAA,CAAS,MAAM,CAAA;AAC7C,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,WAAA,EAAa;AAChB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,OAAO,SAAA,CAAU,OAAA;AACvB,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA;AAAA,IACF;AACA,IAAA,MAAM,OAAO,MAAY;AACvB,MAAA,KAAA,MAAW,KAAA,IAAS,IAAA,CAAK,gBAAA,CAAiB,cAAc,CAAA,EAAG;AACzD,QAAA,MAAM,OAAA,GAAU,KAAA;AAChB,QAAA,IAAI,CAAC,QAAQ,MAAA,EAAQ;AACnB,UAAA,OAAA,CAAQ,KAAA,EAAM;AAAA,QAChB;AAAA,MACF;AAAA,IACF,CAAA;AACA,IAAA,IAAA,EAAK;AACL,IAAA,IAAA,CAAK,gBAAA,CAAiB,MAAA,EAAQ,IAAA,EAAM,IAAI,CAAA;AACxC,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,CAAK,mBAAA,CAAoB,MAAA,EAAQ,IAAA,EAAM,IAAI,CAAA;AAAA,IAC7C,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAA,MAAM,SAAA,GAAYV,aAAA;AAAA,IAChB,MAAM,YAAA,CAAa,IAAA,EAAM,CAAA,CAAE,sBAAsB,CAAA;AAAA,IACjD,CAAC,IAAA,EAAM,CAAA,CAAE,sBAAsB;AAAA,GACjC;AAKA,EAAA,MAAM,SAAA,GAAY,WAAW,WAAA,GAAc,OAAA;AAC3C,EAAA,MAAM,QAAA,GAAWa,qCAAmB,SAAS,CAAA;AAG7C,EAAA,MAAM,SAAA,GAAYJ,aAA0B,IAAI,CAAA;AAGhD,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,MAAM,QAAA;AAAA,EACR;AAEA,EAAA,IAAI,CAAC,QAAA,IAAY,gBAAA,EAAkB,MAAA,KAAW,MAAA,EAAW;AACvD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,WAAA,EAAc,IAAA,CAAK,EAAE,CAAA,8BAAA,EAAiC,UAAU,CAAA,6YAAA;AAAA,KAMlE;AAAA,EACF;AAEA,EAAA,MAAM,YAAY,KAAA,KAAU,WAAA;AAC5B,EAAA,MAAM,QAAA,GAAW,QAAA,KAAa,IAAA,IAAQ,SAAA,IAAa,QAAA;AACnD,EAAA,MAAM,IAAA,GAAO,KAAA,CAAM,IAAA,KAAS,WAAA,IAAe,MAAM,IAAA,KAAS,WAAA;AAC1D,EAAA,MAAM,SAAA,GAAY,aAAa,MAAA,GAAY,MAAA,GAAY,KAAK,GAAA,CAAI,CAAA,EAAG,QAAA,GAAW,QAAA,CAAS,SAAS,CAAA;AAGhG,EAAA,MAAM,KAAA,GAAQ,CAAC,IAAA,KAA4B;AACzC,IAAA,aAAA,CAAc,CAAC,KAAA,KAAU,KAAA,GAAQ,CAAC,CAAA;AAClC,IAAA,QAAA,CAAS,IAAI,CAAA;AAAA,EACf,CAAA;AAQA,EAAA,MAAM,eAAA,GAAkB,CAACE,KAAAA,KAAuB;AAC9C,IAAA,eAAA,CAAgB,OAAA,GAAUA,KAAAA;AAC1B,IAAA,IAAA,EAAM,SAAA,CAAUA,OAAM,WAAW,CAAA;AAAA,EACnC,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,OAChB,GAAA,EACA,OAAA,EACAA,KAAAA,KACkB;AAClB,IAAA,MAAM,MAAA,GAAS,WAAW,OAAA,EAAS,MAAA;AACnC,IAAA,MAAM,UAAA,GAAa,SAAS,UAAA,IAAc,CAAA;AAC1C,IAAA,MAAM,QAAQ,QAAA,CAAS,SAAA;AACvB,IAAA,MAAM,MAAM,MAAA,CAAO,OAAA;AACnB,IAAA,IAAI,WAAW,MAAA,EAAW;AAGxB,MAAA,QAAA,CAAS,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,EAAAA,OAAM,CAAA;AACrC,MAAA;AAAA,IACF;AACA,IAAA,QAAA,CAAS,EAAE,IAAA,EAAM,WAAA,EAAa,IAAA,EAAAA,OAAM,CAAA;AACpC,IAAA,IAAA,CAAK,sBAAA,EAAwB,EAAE,KAAA,EAAO,UAAA,EAAY,CAAA;AAClD,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI;AACF,MAAA,MAAA,GAAS,MAAM,OAAO,GAAG,CAAA;AAAA,IAC3B,CAAA,CAAA,MAAQ;AAIN,MAAA,IAAI,GAAA,KAAQ,OAAO,OAAA,EAAS;AAC1B,QAAA;AAAA,MACF;AACA,MAAA,KAAA,CAAM,EAAE,IAAA,EAAM,eAAA,EAAiB,MAAAA,KAAAA,EAAM,SAAA,EAAW,MAAM,CAAA;AACtD,MAAA,IAAA,CAAK,qBAAqB,EAAE,KAAA,EAAO,UAAA,EAAY,SAAA,EAAW,MAAM,CAAA;AAChE,MAAA;AAAA,IACF;AACA,IAAA,IAAI,GAAA,KAAQ,OAAO,OAAA,EAAS;AAC1B,MAAA;AAAA,IACF;AAIA,IAAA,MAAM,MAAA,GAASG,mCAAiB,MAAM,CAAA;AACtC,IAAA,IAAI,MAAA,CAAO,WAAW,QAAA,EAAU;AAC9B,MAAA,KAAA,CAAM,EAAE,MAAM,eAAA,EAAiB,IAAA,EAAAH,OAAM,SAAA,EAAW,MAAA,CAAO,WAAW,CAAA;AAClE,MAAA,IAAA,CAAK,qBAAqB,EAAE,KAAA,EAAO,YAAY,SAAA,EAAW,MAAA,CAAO,WAAW,CAAA;AAC5E,MAAA;AAAA,IACF;AACA,IAAA,IAAI,MAAA,CAAO,WAAW,YAAA,EAAc;AAIlC,MAAA,QAAA,CAAS,EAAE,MAAM,YAAA,EAAc,IAAA,EAAAA,OAAM,IAAA,EAAM,MAAA,CAAO,MAAM,CAAA;AACxD,MAAA,IAAA,CAAK,mBAAA,EAAqB;AAAA,QACxB,KAAA;AAAA,QACA,UAAA;AAAA,QACA,SAAA,EAAW,IAAA;AAAA,QACX,GAAI,OAAO,IAAA,KAAS,MAAA,GAAY,EAAE,IAAA,EAAM,MAAA,CAAO,IAAA,EAAK,GAAI;AAAC,OAC1D,CAAA;AACD,MAAA;AAAA,IACF;AACA,IAAA,MAAM,YAAY,YAAA,EAAa;AAC/B,IAAA,QAAA,CAAS,EAAE,IAAA,EAAM,QAAA,EAAU,IAAA,EAAAA,KAAAA,EAAM,UAAA,EAAY,MAAA,CAAO,UAAA,EAAY,KAAA,EAAO,MAAA,CAAO,KAAA,EAAO,CAAA;AACrF,IAAA,MAAM,QAAQ,MAAA,CAAO,KAAA;AACrB,IAAA,IAAI,UAAU,IAAA,EAAM;AAMlB,MAAA,IAAA,CAAK,qBAAqB,EAAE,KAAA,EAAO,UAAA,EAAY,SAAA,EAAW,MAAM,CAAA;AAChE,MAAA;AAAA,IACF;AAcA,IAAA,UAAA;AAAA,MACEI,2BAAA;AAAA,QACE;AAAA,UACE,OAAO,KAAA,CAAM,KAAA;AAAA,UACb,UAAU,KAAA,CAAM,QAAA;AAAA,UAChB,QAAQ,KAAA,CAAM,MAAA;AAAA,UACd,SAAA;AAAA,UACA,aAAA,EAAeC,mBAAY,sBAAA,CAAuB;AAAA,YAChD,KAAA,EAAOC,iCAAA;AAAA,YACP,MAAA,EAAQ;AAAA,cACN,EAAA,EAAIC,6BAAA,CAAY,IAAA,CAAK,EAAE,CAAA;AAAA,cACvB,IAAA,EAAM,EAAE,CAAC,IAAA,CAAK,UAAU,OAAO,GAAG,KAAK,KAAA,EAAM;AAAA,cAC7C,GAAGC,yBAAkB,IAAI;AAAA,aAC3B;AAAA,YACA,WAAA,EAAa,SAAA;AAAA,YACb,QAAA,EAAU,OAAO,cAAA,IAAkB,EAAA;AAAA;AAAA;AAAA;AAAA,YAInC,aAAA,EAAe;AAAA,cACb,KAAA,EAAOC,kCAAgB,KAAK,CAAA;AAAA,cAC5B,QAAA,EAAU,CAAA;AAAA,cACV,QAAQ,KAAA,CAAM,MAAA;AAAA,cACd,UAAU,KAAA,CAAM,QAAA;AAAA,cAChB,OAAA,EAAS,KAAA,CAAM,OAAA,IAAW;AAAC;AAC7B,WACD;AAAA,SACH;AAAA,QACAT;AAAA;AACF,KACF;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,OAAO,OAAA,KAAgD;AACxE,IAAA,MAAM,UAAU,UAAA,CAAW,OAAA;AAC3B,IAAA,IAAI,QAAA,IAAY,aAAA,CAAc,OAAA,IAAW,OAAA,KAAY,MAAA,EAAW;AAC9D,MAAA;AAAA,IACF;AACA,IAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AACxB,IAAA,MAAM,QAAQ,QAAA,CAAS,SAAA;AACvB,IAAA,MAAM,MAAM,MAAA,CAAO,OAAA;AAInB,IAAA,MAAMA,QAAOU,0BAAA,EAAS;AACtB,IAAA,eAAA,CAAgBV,KAAI,CAAA;AACpB,IAAA,IAAI;AACF,MAAA,IAAI,GAAA,GAA2B,IAAA;AAC/B,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA,QAAA,CAAS,EAAE,IAAA,EAAM,WAAA,EAAa,IAAA,EAAAA,OAAM,CAAA;AACpC,QAAA,IAAI;AACF,UAAA,GAAA,GAAM,MAAM,OAAA,CAAQ,MAAA,CAAO,OAAO,CAAA;AAAA,QACpC,CAAA,CAAA,MAAQ;AACN,UAAA,IAAI,GAAA,KAAQ,OAAO,OAAA,EAAS;AAC1B,YAAA;AAAA,UACF;AAGA,UAAA,KAAA,CAAM,EAAE,IAAA,EAAM,eAAA,EAAiB,IAAA,EAAAA,OAAM,CAAA;AACrC,UAAA,IAAA,CAAK,yBAAA,EAA2B;AAAA,YAC9B,KAAA;AAAA,YACA,YAAY,OAAA,CAAQ,UAAA;AAAA,YACpB,SAAA,EAAW;AAAA,WACZ,CAAA;AACD,UAAA;AAAA,QACF;AAMA,QAAA,IAAI,GAAA,KAAQ,OAAO,OAAA,EAAS;AAC1B,UAAA;AAAA,QACF;AACA,QAAA,WAAA,CAAY,GAAG,CAAA;AACf,QAAA,IAAA,CAAK,sBAAsB,EAAE,KAAA,EAAO,UAAA,EAAY,OAAA,CAAQ,YAAY,CAAA;AAAA,MACtE;AACA,MAAA,QAAA,GAAWI,4BAAU,UAAA,CAAW,GAAA,EAAK,KAAK,CAAA,EAAGJ,KAAI,CAAC,CAAA;AAClD,MAAA,IAAA,CAAK,aAAa,EAAE,KAAA,EAAO,YAAY,OAAA,EAAS,UAAA,IAAc,GAAG,CAAA;AACjE,MAAA,IAAI,MAAA,IAAU,QAAQ,IAAA,EAAM;AAI1B,QAAA,QAAA,EAAS;AACT,QAAA,QAAA,CAAS,EAAE,IAAA,EAAM,WAAA,EAAa,IAAA,EAAAA,OAAM,CAAA;AACpC,QAAA;AAAA,MACF;AACA,MAAA,MAAM,SAAA,CAAU,GAAA,EAAK,OAAA,EAASA,KAAI,CAAA;AAAA,IACpC,CAAA,SAAE;AACA,MAAA,aAAA,CAAc,OAAA,GAAU,KAAA;AAAA,IAC1B;AAAA,EACF,CAAA;AAOA,EAAA,MAAM,WAAA,GAAc,OAAO,GAAA,EAAmBA,KAAAA,KAAgC;AAC5E,IAAA,IAAI,cAAc,OAAA,EAAS;AACzB,MAAA;AAAA,IACF;AACA,IAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AACxB,IAAA,eAAA,CAAgBA,KAAI,CAAA;AACpB,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,CAAU,GAAA,EAAK,QAAA,CAAS,IAAA,EAAMA,KAAI,CAAA;AAAA,IAC1C,CAAA,SAAE;AACA,MAAA,aAAA,CAAc,OAAA,GAAU,KAAA;AAAA,IAC1B;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,YAAA,GAAe,CAAC,KAAA,KAAiC;AACrD,IAAA,KAAA,CAAM,cAAA,EAAe;AACrB,IAAA,IAAI,YAAY,IAAA,IAAQ,QAAA,CAAS,SAAS,IAAA,IAAQ,KAAA,CAAM,SAAS,MAAA,EAAQ;AACvE,MAAA;AAAA,IACF;AACA,IAAA,KAAK,UAAA,CAAW,SAAS,IAAI,CAAA;AAAA,EAC/B,CAAA;AAEA,EAAA,MAAM,YAAY,MAAY;AAC5B,IAAA,IAAI,QAAA,IAAY,IAAA,IAAQ,CAAC,QAAA,CAAS,SAAA,EAAW;AAC3C,MAAA;AAAA,IACF;AACA,IAAA,IAAI,QAAA,CAAS,SAAS,IAAA,EAAM;AAC1B,MAAA,IAAA,CAAK,qBAAA,EAAuB;AAAA,QAC1B,OAAO,QAAA,CAAS,SAAA;AAAA,QAChB,UAAA,EAAY,SAAS,IAAA,CAAK;AAAA,OAC3B,CAAA;AAAA,IACH;AACA,IAAA,QAAA,CAAS,EAAE,IAAA,EAAM,MAAA,EAAQ,CAAA;AACzB,IAAA,WAAA,CAAY,IAAI,CAAA;AAChB,IAAA,KAAA,EAAM;AACN,IAAA,KAAK,SAAS,KAAA,EAAM;AAAA,EACtB,CAAA;AAKA,EAAA,MAAM,cAAc,OAAO,IAAA,CAAK,MAAA,KAAW,QAAA,GAAW,KAAK,MAAA,GAAS,MAAA;AACpE,EAAA,MAAM,UAAA,GAAaW,mCAAA,CAAkB,IAAA,CAAK,MAAM,CAAA;AAMhD,EAAA,MAAM,cAAA,GAAiBtB,cAAQ,MAAMuB,6BAAA,CAAY,OAAO,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AACpE,EAAA,MAAM,YAAA,GAAevB,cAAQ,MAAMwB,8BAAA,CAAa,UAAU,CAAA,EAAG,CAAC,UAAU,CAAC,CAAA;AACzE,EAAA,MAAM,WAAA,GACJ,gBAAgB,IAAA,KAAS,QAAA,IAAY,eAAe,MAAA,KAAW,QAAA,GAC3D,eAAe,KAAA,GACf,IAAA;AAGN,EAAA,MAAM,WAAA,GAAcxB,aAAA;AAAA,IAClB,MACE,MAAA,CAAO,QAAA,IAAY,QAAA,IAAY,YAAA,KAAiB,IAAA,IAAQ,WAAA,EAAa,OAAA,KAAY,MAAA,GAC7E,gBAAA,CAAiB,WAAA,CAAY,OAAO,CAAA,GACpC,IAAA;AAAA,IACN,CAAC,MAAA,CAAO,QAAA,EAAU,QAAA,EAAU,cAAc,WAAW;AAAA,GACvD;AAGA,EAAA,MAAM,kBAAA,GAAqB,CAAC,MAAA,CAAO,QAAA,GAC/B,IAAA,GACA,QAAA,GACE,YAAA,GACA,KAAA,CAAM,IAAA,KAAS,QAAA,GACb,KAAA,CAAM,UAAA,GACN,IAAA;AACR,EAAA,MAAM,gBAAgB,QAAA,GAAW,WAAA,GAAc,MAAM,IAAA,KAAS,QAAA,GAAW,MAAM,KAAA,GAAQ,IAAA;AAIvF,EAAA,MAAM,aAAA,GAAgB,QAAA,CAAS,OAAA,GAAU,IAAA,GAAO,SAAA;AAchD,EAAA,MAAM,OAAA,GAAUS,aAAwB,IAAI,CAAA;AAC5C,EAAA,MAAM,CAAC,iBAAA,EAAmB,oBAAoB,CAAA,GAAIF,eAAS,KAAK,CAAA;AAIhE,EAAA,MAAM,aAAA,GAAgB,kBAAA,KAAuB,IAAA,IAAQ,aAAA,KAAkB,IAAA;AACvE,EAAA,MAAM,iBAAA,GAAoB,iBAAiB,CAAC,iBAAA;AAI5C,EAAAG,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,OAAO,OAAA,CAAQ,OAAA;AACrB,IAAA,MAAM,OAAO,aAAA,KAAkB,IAAA,KAAS,QAAQ,IAAA,CAAK,aAAA,CAAc,cAAc,CAAA,KAAM,IAAA,CAAA;AACvF,IAAA,oBAAA,CAAqB,CAAC,GAAA,KAAS,GAAA,KAAQ,IAAA,GAAO,MAAM,IAAK,CAAA;AAAA,EAC3D,CAAC,CAAA;AAED,EAAA,MAAM,YAAA,GAAeV,cAA6B,MAAM;AACtD,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,OAAO,IAAA;AAAA,IACT;AAOA,IAAA,IAAI,QAAA,CAAS,WAAW,WAAA,EAAa;AACnC,MAAA,OAAO,EAAE,IAAA,EAAM,CAAA,CAAE,yBAAA,EAA2B,UAAU,IAAA,EAAK;AAAA,IAC7D;AAIA,IAAA,MAAM,OAAA,GACJ,QAAA,CAAS,MAAA,KAAW,UAAA,IAAc,SAAS,IAAA,GACvC;AAAA,MACE,MAAM,CAAA,CAAE,yBAAA;AAAA,QACN,aAAA,CAAc,IAAA,CAAK,UAAA,EAAY,UAAA,EAAY,SAAS,CAAA;AAAA,QACpD;AAAA,OACF;AAAA,MACA,QAAA,EAAU;AAAA,KACZ,GACA,IAAA;AACN,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,IAAI,KAAA,CAAM,IAAA,KAAS,WAAA,IAAe,SAAA,EAAW;AAC3C,QAAA,OAAO,EAAE,IAAA,EAAM,CAAA,CAAE,eAAA,EAAiB,UAAU,IAAA,EAAK;AAAA,MACnD;AACA,MAAA,OAAO,KAAA,CAAM,IAAA,KAAS,MAAA,GAAS,OAAA,GAAU,IAAA;AAAA,IAC3C;AACA,IAAA,QAAQ,MAAM,IAAA;AAAM,MAClB,KAAK,QAAA,EAAU;AACb,QAAA,MAAM,QAAQ,KAAA,CAAM,KAAA;AAIpB,QAAA,IAAI,UAAU,IAAA,EAAM;AAClB,UAAA,OAAO,EAAE,IAAA,EAAM,CAAA,EAAG,CAAA,CAAE,eAAe,IAAI,CAAA,CAAE,gBAAgB,CAAA,CAAA,EAAI,QAAA,EAAU,IAAA,EAAK;AAAA,QAC9E;AAEA,QAAA,IAAI,CAAC,OAAO,QAAA,EAAU;AACpB,UAAA,OAAO,EAAE,IAAA,EAAM,CAAA,CAAE,eAAA,EAAiB,UAAU,IAAA,EAAK;AAAA,QACnD;AACA,QAAA,OAAO;AAAA,UACL,IAAA,EAAM,CAAA,EAAG,CAAA,CAAE,eAAe,CAAA,CAAA,EAAI,CAAA,CAAE,iBAAA,CAAkBL,gCAAA,CAAe,KAAK,CAAA,EAAG,KAAA,CAAM,MAAM,CAAC,CAAA,CAAA;AAAA,UACtF,UAAU,KAAA,CAAM;AAAA,SAClB;AAAA,MACF;AAAA,MACA,KAAK,YAAA;AACH,QAAA,OAAO,EAAE,MAAM,iBAAA,CAAkB,KAAA,CAAM,MAAM,CAAC,CAAA,EAAG,UAAU,IAAA,EAAK;AAAA,MAClE,KAAK,YAAA;AACH,QAAA,OAAO,EAAE,IAAA,EAAM,CAAA,CAAE,8BAAA,EAAgC,UAAU,IAAA,EAAK;AAAA,MAClE,KAAK,MAAA;AACH,QAAA,OAAO,OAAA;AAAA,MACT;AAIE,QAAA,OAAO,IAAA;AAAA;AACX,EACF,CAAA,EAAG,CAAC,QAAA,EAAU,MAAA,EAAQ,KAAA,EAAO,SAAA,EAAW,CAAA,EAAG,QAAA,CAAS,MAAA,EAAQ,IAAA,EAAM,UAAA,EAAY,MAAA,CAAO,QAAQ,CAAC,CAAA;AAE9F,EAAA,MAAM,YACJ,QAAA,CAAS,KAAA,KAAU,OACf,CAAA,CAAE,sBAAA,CAAuB,SAAS,KAAK,CAAA,GACvC,KAAA,CAAM,IAAA,KAAS,kBACb,CAAA,CAAE,qBAAA,GACF,MAAM,IAAA,KAAS,eAAA,GACb,EAAE,yBAAA,GACF,IAAA;AAEV,EAAA,uBACEH,eAAA;AAAA,IAAC,MAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,OAAA;AAAA,MACV,GAAA,EAAK,OAAA;AAAA,MAEL,mBAAiB,eAAA,CAAgB,IAAA,CAAK,IAAA,CAAK,KAAK,IAAI,OAAA,GAAU,MAAA;AAAA,MAC9D,IAAA,EAAM,MAAA;AAAA,MACN,kBAAA,EAAkB,UAAA;AAAA,MAClB,KAAA,EAAO,KAAA;AAAA,MACP,QAAA,EAAU,YAAA;AAAA,MAOV,QAAA,EAAA;AAAA,wBAAAE,cAAA,CAAC,GAAA,EAAA,EAAE,EAAA,EAAI,OAAA,EAAS,SAAA,EAAU,aAAA,EAAc,GAAA,EAAK,UAAA,IAAc,MAAA,EAAQ,IAAA,EAAM,WAAA,EACtE,QAAA,EAAA,IAAA,CAAK,KAAA,EACR,CAAA;AAAA,QAEC,OAAO,IAAA,CAAK,YAAA,KAAiB,YAAY,IAAA,CAAK,YAAA,KAAiB,qBAC9DA,cAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,oBAAA,EAAqB,KAAK,UAAA,IAAc,MAAA,EAAQ,MAAM,WAAA,EAChE,QAAA,EAAA,IAAA,CAAK,cACR,CAAA,GACE,IAAA;AAAA,wBAIJA,cAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,iBAAA,EAAkB,GAAA,EAAK,cAAc,MAAA,EAAQ,IAAA,EAAM,WAAA,EAC7D,QAAA,EAAA,IAAA,CAAK,aAAA,EACR,CAAA;AAAA,QAEC,IAAA,CAAK,KAAA,KAAU,MAAA,IAAa,SAAA,KAAc,MAAA,mCACxC,KAAA,EAAA,EAAI,SAAA,EAAU,cAAA,EAAe,GAAA,EAAK,SAAA,EAMhC,QAAA,EAAA;AAAA,UAAA,IAAA,CAAK,KAAA,KAAU,MAAA,mBACdF,eAAA,CAAC,UAAA,EAAA,EAAS,WAAU,aAAA,EAClB,QAAA,EAAA;AAAA,4BAAAE,cAAA,CAAC,QAAA,EAAA,EAAO,SAAA,EAAU,mBAAA,EAAqB,QAAA,EAAA,CAAA,CAAE,uBAAA,EAAwB,CAAA;AAAA,4BAMjEA,cAAA;AAAA,cAAC+B,+BAAA;AAAA,cAAA;AAAA,gBACC,OAAO,IAAA,CAAK,KAAA;AAAA,gBACZ,UAAA;AAAA,gBACA,aAAA,EAAe,WAAA;AAAA,gBACd,GAAI,WAAA,KAAgB,MAAA,GAAY,EAAE,WAAA,KAAgB,EAAC;AAAA,gBACnD,GAAI,YAAA,KAAiB,MAAA,GAAY,EAAE,YAAA,KAAiB,EAAC;AAAA,gBACrD,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY,EAAC;AAAA,gBAC3C,GAAI,aAAA,KAAkB,MAAA,GAAY,EAAE,aAAA,KAAkB,EAAC;AAAA,gBACvD,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,KAAW,EAAC;AAAA,gBACzC,GAAI,aAAa,IAAA,IAAQ,WAAA,GAAc,EAAE,QAAA,EAAU,IAAA,KAAS;AAAC;AAAA;AAChE,WAAA,EACF,CAAA,GACE,IAAA;AAAA,UACH,cAAc,MAAA,mBACbjC,eAAA,CAAC,cAAS,SAAA,EAAU,aAAA,EAAc,aAAU,MAAA,EAC1C,QAAA,EAAA;AAAA,4BAAAE,cAAA,CAAC,QAAA,EAAA,EAAO,SAAA,EAAU,mBAAA,EAAqB,QAAA,EAAA,CAAA,CAAE,sBAAA,EAAuB,CAAA;AAAA,4BAEhEA,cAAA;AAAA,cAAC+B,+BAAA;AAAA,cAAA;AAAA,gBACC,KAAA,EAAO,SAAA;AAAA,gBACP,UAAA;AAAA,gBACA,aAAA,EAAe,WAAA;AAAA,gBACd,GAAI,YAAA,KAAiB,MAAA,GAAY,EAAE,YAAA,KAAiB,EAAC;AAAA,gBACrD,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY,EAAC;AAAA,gBAC3C,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,KAAW,EAAC;AAAA,gBACzC,GAAI,aAAa,IAAA,IAAQ,WAAA,GAAc,EAAE,QAAA,EAAU,IAAA,KAAS;AAAC;AAAA;AAChE,WAAA,EACF,CAAA,GACE;AAAA,SAAA,EACN,CAAA,GACE,IAAA;AAAA,QAIH,QAAA,IAAY,YAAY,IAAA,mBACvBjC,eAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAU,gBAAA;AAAA,YACV,eAAa,QAAA,CAAS,MAAA;AAAA,YACtB,YAAA,EACE,QAAA,CAAS,MAAA,KAAW,WAAA,GAChB,IAAA,CAAK,GAAA,CAAI,WAAA,EAAa,IAAA,CAAK,KAAA,CAAM,QAAA,CAAS,KAAA,GAAQ,WAAW,CAAC,CAAA,GAC9D,MAAA;AAAA,YAGL,QAAA,EAAA;AAAA,cAAA,QAAA,CAAS,WAAW,WAAA,mBACnBE,cAAA;AAAA,gBAAC,QAAA;AAAA,gBAAA;AAAA,kBACC,IAAA,EAAK,QAAA;AAAA,kBACL,SAAA,EAAU,YAAA;AAAA,kBACV,QAAA,EAAU,QAAA;AAAA,kBACV,OAAA,EAAS,MAAM,QAAA,CAAS,IAAA,EAAK;AAAA,kBAE5B,QAAA,EAAA,CAAA,CAAE;AAAA;AAAA,eACL;AAAA;AAAA;AAAA;AAAA,gCAKAA,cAAA;AAAA,kBAAC,QAAA;AAAA,kBAAA;AAAA,oBACC,IAAA,EAAK,QAAA;AAAA,oBACL,SAAA,EAAU,cAAA;AAAA,oBACV,QAAA,EAAU,QAAA;AAAA,oBACV,eAAA,EAAe,CAAC,QAAA,CAAS,SAAA,IAAa,IAAA,IAAQ,MAAA;AAAA,oBAC9C,OAAA,EAAS,SAAA;AAAA,oBAER,QAAA,EAAA,QAAA,CAAS,IAAA,KAAS,IAAA,GAAO,CAAA,CAAE,kBAAkB,CAAA,CAAE;AAAA;AAAA;AAClD,eAAA;AAAA,cAED,SAAS,MAAA,KAAW,WAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,+CAMlB,GAAA,EAAA,EAAE,SAAA,EAAU,gBAAA,EAAiB,aAAA,EAAY,QACvC,QAAA,EAAA,CAAA,CAAE,0BAAA;AAAA,kBACD,aAAA,CAAc,QAAA,CAAS,SAAA,EAAW,UAAA,EAAY,MAAM,CAAA;AAAA,kBACpD;AAAA,iBACF,EACF;AAAA,kBACE,IAAA;AAAA,cACH,SAAA,KAAc,MAAA,IAAa,QAAA,KAAa,MAAA,mBACvCA,cAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,aAAA,EAAe,QAAA,EAAA,CAAA,CAAE,uBAAA,CAAwB,SAAA,EAAW,QAAQ,GAAE,CAAA,GACzE;AAAA;AAAA;AAAA,SACN;AAAA,QAGD,SAAA,KAAc,IAAA,GAAO,IAAA,GAAO,QAAA,CAAS,OAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yCAKnC,GAAA,EAAA,EAAE,SAAA,EAAU,0BAAyB,IAAA,EAAK,MAAA,EACxC,YAAE,4BAAA,EACL;AAAA;AAAA;AAAA,0BAGAA,cAAA;AAAA,YAAC,OAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAU,YAAA;AAAA,cACV,QAAA,EAAQ,IAAA;AAAA,cACR,GAAA,EAAK,SAAA;AAAA,cACL,cAAY,CAAA,CAAE,sBAAA;AAAA,cACd,SAAS,CAAC,KAAA,KAAU,QAAA,CAAS,MAAA,CAAO,MAAM,aAAa,CAAA;AAAA,cACtD,GAAI,QAAA,GAAW,KAAK,EAAE,WAAA,EAAa,SAAS,SAAA;AAAU;AAAA;AACzD,SAAA;AAAA,QAGD,YAAY,SAAA,GAAY,IAAA,mBACvBF,eAAA,CAAC,KAAA,EAAA,EAAI,WAAU,eAAA,EACb,QAAA,EAAA;AAAA,0BAAAE,cAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,GAAA,EAAK,SAAA;AAAA,cACL,IAAA,EAAK,QAAA;AAAA,cACL,SAAA,EAAU,cAAA;AAAA,cACV,UAAU,QAAA,KAAa,IAAA;AAAA,cACvB,iBAAe,QAAA,CAAS,IAAA,KAAS,IAAA,IAAQ,KAAA,CAAM,SAAS,MAAA,IAAU,MAAA;AAAA,cAClE,aAAW,IAAA,IAAQ,MAAA;AAAA,cAElB,QAAA,EAAA,CAAA,CAAE;AAAA;AAAA,WACL;AAAA,UACC,MAAA;AAAA;AAAA;AAAA;AAAA,4BAICA,cAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,SAAA,EAAU,oBAAA;AAAA,gBACV,QAAA,EAAU,QAAA;AAAA,gBACV,iBAAe,IAAA,IAAQ,MAAA;AAAA,gBACvB,SAAS,MAAM;AACb,kBAAA,IAAI,CAAC,IAAA,EAAM;AACT,oBAAA,KAAK,WAAW,IAAI,CAAA;AAAA,kBACtB;AAAA,gBACF,CAAA;AAAA,gBAEC,QAAA,EAAA,CAAA,CAAE;AAAA;AAAA;AACL,cACE,IAAA;AAAA,UACH,WAAA,CAAY,KAAA,EAAO,QAAQ,CAAA,mBAC1BA,cAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,SAAA,EAAU,aAAA;AAAA,cACV,SAAS,MAAM;AAKb,gBAAA,SAAA,CAAU,SAAS,KAAA,EAAM;AACzB,gBAAA,IAAI,KAAA,CAAM,SAAS,eAAA,EAAiB;AAClC,kBAAA,KAAK,UAAA,CAAW,SAAS,IAAI,CAAA;AAAA,gBAC/B,CAAA,MAAA,IAAW,KAAA,CAAM,IAAA,KAAS,eAAA,IAAmB,aAAa,IAAA,EAAM;AAC9D,kBAAA,KAAK,WAAA,CAAY,QAAA,EAAU,KAAA,CAAM,IAAI,CAAA;AAAA,gBACvC;AAAA,cACF,CAAA;AAAA,cAEC,QAAA,EAAA,CAAA,CAAE;AAAA;AAAA,WACL,GACE;AAAA,SAAA,EACN,CAAA;AAAA,QAGD,uBACCA,cAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,eAAA,EAAgB,MAAK,QAAA,EAAS,WAAA,EAAU,MAAA,EAClD,QAAA,EAAA,KAAA,CAAM,SAAS,WAAA,GAAc,CAAA,CAAE,kBAAA,GAAqB,CAAA,CAAE,oBACzD,CAAA,GACE,IAAA;AAAA,QAEH,SAAA,KAAc,IAAA,mBACbA,cAAA,CAAC,GAAA,EAAA,EAAmB,SAAA,EAAU,eAAc,IAAA,EAAK,OAAA,EAC9C,QAAA,EAAA,SAAA,EAAA,EADK,UAER,CAAA,GACE,IAAA;AAAA,QAEH,uBAAuB,IAAA,mBACtBA,cAAA;AAAA,UAACgC,uCAAA;AAAA,UAAA;AAAA,YACC,MAAM,EAAE,aAAA,EAAe,KAAK,aAAA,EAAe,MAAA,EAAQ,KAAK,MAAA,EAAO;AAAA,YAC/D,UAAA,EAAY,kBAAA;AAAA,YACX,GAAI,aAAA,KAAkB,IAAA,GAAO,EAAE,KAAA,EAAO,aAAA,KAAkB,EAAC;AAAA,YACzD,GAAI,aAAA,KAAkB,IAAA,GAAO,EAAE,QAAA,EAAU,aAAA,KAAkB,EAAC;AAAA,YAC5D,GAAI,cAAA,KAAmB,MAAA,GAAY,EAAE,cAAA,KAAmB,EAAC;AAAA,YACzD,GAAI,iBAAA,KAAsB,MAAA,GAAY,EAAE,iBAAA,KAAsB,EAAC;AAAA,YAC/D,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,KAAW,EAAC;AAAA,YACzC,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY;AAAC;AAAA,SAC9C,GACE,IAAA;AAAA,QAEH,WAAA,KAAgB,IAAA,mBACflC,eAAA,CAAC,KAAA,EAAA,EAAI,WAAU,aAAA,EAGb,QAAA,EAAA;AAAA,0BAAAE,cAAA,CAAC,IAAA,EAAA,EAAG,SAAA,EAAU,aAAA,EAAc,YAAA,EAAY,CAAA,CAAE,uBAAA,EAAyB,GAAA,EAAK,UAAA,EACrE,QAAA,EAAA,WAAA,CAAY,GAAA,CAAI,CAAC,KAAA,EAAO,KAAA,KAAU;AACjC,YAAA,MAAM,QAAA,GAAWiC,8BAAA,CAAa,KAAA,EAAO,CAAC,CAAA;AACtC,YAAA,uBACEnC,eAAA;AAAA,cAAC,IAAA;AAAA,cAAA;AAAA,gBAGC,SAAA,EAAU,YAAA;AAAA,gBACV,cAAY,KAAA,CAAM,KAAA;AAAA,gBAKlB,QAAA,EAAA;AAAA,kCAAAE,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,oBAAA,EAAqB,KAAA,EAAOkC,mCACzC,QAAA,EAAA,QAAA,EACH,CAAA;AAAA,kCACAlC,cAAA;AAAA,oBAAC,MAAA;AAAA,oBAAA;AAAA,sBACC,SAAA,EAAU,iBAAA;AAAA,sBACV,aAAA,EAAY,MAAA;AAAA,sBACZ,KAAK,UAAA,IAAc,MAAA;AAAA,sBACnB,IAAA,EAAM,WAAA;AAAA,sBAEL,QAAA,EAAA,KAAA,CAAM;AAAA;AAAA;AACT;AAAA,eAAA;AAAA,cAjBK;AAAA,aAkBP;AAAA,UAEJ,CAAC,CAAA,EACH,CAAA;AAAA,yCAEC,IAAA,EAAA,EAAG,SAAA,EAAU,gBACX,QAAA,EAAA,aAAA,CAAc,GAAA,CAAI,CAACmC,MAAAA,qBAClBnC,cAAA,CAAC,IAAA,EAAA,EAAe,YAAA,EAAYmC,QACzB,QAAA,EAAA,CAAA,CAAE,mBAAA,CAAoBA,MAAK,CAAA,EAAA,EADrBA,MAET,CACD,CAAA,EACH;AAAA,SAAA,EACF,CAAA,GACE,IAAA;AAAA,uCAEHC,gCAAA,EAAA,EAAe,EAAA,EAAI,CAAA,EAAG,IAAA,CAAK,EAAE,CAAA,SAAA,CAAA,EAC3B,QAAA,EAAA,QAAA;AAAA;AAAA;AAAA,UAGC,cAAA,KAAmB,MAAA,KAAc,MAAA,CAAO,QAAA,IAAY,eAAe,IAAA,KAAS,QAAA,CAAA,mBAC1EpC,cAAA,CAAC,cAAA,EAAA,EAAe,OAAA,EAAS,cAAA,EAAgB,CAAA,EAAM,UAAA,EAAY,mBAAmB,CAAA,GAC5E;AAAA,YACF,iBAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0BAMFA,cAAA,CAAC,UAAK,SAAA,EAAU,oBAAA,EAAqB,OAAOkC,iCAAA,EAC1C,QAAA,kBAAAlC,cAAA,CAAC,gBAAA,EAAA,EAAiB,YAAA,EAA4B,CAAA,EAChD;AAAA,4BAEAA,cAAA,CAAC,gBAAA,EAAA,EAAiB,YAAA,EAA4B,CAAA,EAElD;AAAA;AAAA;AAAA,GACF;AAEJ;AC59CO,SAASqC,WAAU,KAAA,EAAuB;AAC/C,EAAA,MAAM,OAAA,GAAUlC,8BAAA,CAAa,KAAA,CAAM,OAAO,CAAA;AAC1C,EAAA,uBACEH,cAAAA,CAACsC,uCAAA,EAAA,EAAsB,aAAA,EAAe,KAAA,CAAM,IAAA,EAAM,KAAA,EAAO,OAAA,EACvD,QAAA,kBAAAtC,cAAAA,CAAC,SAAA,EAAA,EAAe,GAAG,OAAO,CAAA,EAC5B,CAAA;AAEJ","file":"chunk-RT2PP4SO.cjs","sourcesContent":["'use client';\n\n/**\n * Microphone captures that must stop when the region holding them goes away,\n * keyed by group name.\n *\n * Module-level for the reason `playback-group.ts` is: the thing that knows a\n * pane was hidden — the sequence — is not the thing holding the microphone,\n * and a context would have to be threaded through every activity component to\n * reach one. The caller mints the group name per region; a sequence uses the\n * slot's, so hiding one slot stops that slot's capture and no other's. The name\n * reaches a recorder only through `SequenceSlotContext`, never as a prop, so no\n * consumer string can join another region's group.\n *\n * This **supplements** a recorder's own teardown and never replaces it. The\n * sequence's pane effect runs only on a `hidden` transition, not on unmount,\n * and neither a preview nor a standalone render has panes at all — so a\n * component that records still releases the microphone from its own cleanup,\n * and this registry only covers the case where the component is left mounted\n * and merely hidden.\n *\n * No `useCaptureGroup` hook, unlike playback: that one re-registers after every\n * commit because the element behind a ref can be replaced while the ref object\n * is not, whereas a recorder's `stop` keeps one identity for the life of the\n * hook and a plain effect registers it exactly once.\n */\nconst groups = new Map<string, Set<() => void>>();\n\n/** Adds a capture's stop to a group; returns the matching removal. */\nexport function joinCaptureGroup(group: string, stop: () => void): () => void {\n let members = groups.get(group);\n if (members === undefined) {\n members = new Set();\n groups.set(group, members);\n }\n members.add(stop);\n return () => {\n const current = groups.get(group);\n if (current === undefined) {\n return;\n }\n current.delete(stop);\n if (current.size === 0) {\n groups.delete(group);\n }\n };\n}\n\n/**\n * Stops every capture in the group.\n *\n * The members are copied before they are called. Each `stop` is expected to\n * run its own removal, and a component that answers one by starting again\n * would otherwise be stopped by the sweep that its own stop provoked.\n */\nexport function stopCaptureGroup(group: string): void {\n const members = groups.get(group);\n if (members === undefined) {\n return;\n }\n for (const stop of [...members]) {\n stop();\n }\n}\n","'use client';\n\nimport { READ_ALOUD_MAX_SECONDS, READ_ALOUD_MAX_TAKES } from '@intellectif/lk-core';\n\n/** The bounds a take is captured under, as the recorder and the sentences use them. */\nexport interface RecordingBounds {\n /** Above 0, and at most `READ_ALOUD_MAX_SECONDS`. */\n maxSeconds: number;\n /** At least 0 and below `maxSeconds`, or `undefined` for no minimum. */\n minSeconds: number | undefined;\n /** A whole number from 1 to `READ_ALOUD_MAX_TAKES`, or `undefined` for no limit. */\n maxTakes: number | undefined;\n}\n\nconst isNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value);\n\n/**\n * The one reading of an item's `recording`: every bound the component captures\n * under, every sentence that names one, and the key that decides whether a new\n * `data` is a new reading all come from here, so no two of them can read the\n * same field differently.\n *\n * `practice` renders unvalidated content in production, so what comes in may\n * be anything a server stored. What comes out is always a set of bounds the\n * schema accepts, and bounds the schema accepts come out unchanged. Three\n * rules decide the rest, and each errs toward a take the learner can finish:\n *\n * - **A present bound that cannot be read is the schema's own ceiling**, never\n * no bound at all: a `maxSeconds` of `\"20\"` or `NaN` records up to\n * `READ_ALOUD_MAX_SECONDS`, and a `maxTakes` of `\"1\"` allows\n * `READ_ALOUD_MAX_TAKES`. Unreadable data can make the recorder stricter\n * than the ceiling, never looser: `maxTakes: \"1\"` used to mean unlimited\n * takes. `null` is an absent optional, as it is everywhere a host sends JSON.\n * - **A count is a whole number in range**: `0` and `1.5` read as `1`, `999`\n * as the ceiling.\n * - **A minimum the maximum cannot reach is no minimum.** A `minSeconds` of 30\n * under a `maxSeconds` of 20 stopped every take at 20 and then refused it as\n * too short, so the item could never be answered. The schema refuses that\n * pair; a reading that honoured it would leave the learner a dead end.\n */\nexport function readRecordingBounds(recording: unknown): RecordingBounds {\n const bounds =\n typeof recording === 'object' && recording !== null\n ? (recording as Record<string, unknown>)\n : {};\n const maxSeconds =\n isNumber(bounds.maxSeconds) && bounds.maxSeconds > 0\n ? Math.min(bounds.maxSeconds, READ_ALOUD_MAX_SECONDS)\n : READ_ALOUD_MAX_SECONDS;\n const minSeconds =\n isNumber(bounds.minSeconds) && bounds.minSeconds >= 0 && bounds.minSeconds < maxSeconds\n ? bounds.minSeconds\n : undefined;\n const takes = bounds.maxTakes;\n const maxTakes =\n takes === undefined || takes === null\n ? undefined\n : isNumber(takes)\n ? Math.min(Math.max(1, Math.floor(takes)), READ_ALOUD_MAX_TAKES)\n : READ_ALOUD_MAX_TAKES;\n return { maxSeconds, minSeconds, maxTakes };\n}\n\n/**\n * A length of audio as the whole seconds a sentence says beside `maxSeconds`,\n * never more than it. A bound may be fractional — the schema allows 2.5 — and\n * rounding a take that ran to it said \"3 of 2.5 seconds\". Rounded to the\n * nearest second for a finished take, and down for the counter of one still\n * running, which should not reach its bound before the take does.\n */\nexport function secondsWithin(\n milliseconds: number,\n maxSeconds: number,\n rounding: 'nearest' | 'down',\n): number {\n const seconds = milliseconds / 1000;\n const whole = rounding === 'nearest' ? Math.round(seconds) : Math.floor(seconds);\n return Math.min(Math.max(0, whole), maxSeconds);\n}\n","'use client';\n\nimport {\n type ActivityMedia as ActivityMediaData,\n ActivitySchemaError,\n assertRedacted,\n type GradeRecord,\n type InteractionKind,\n type LearnerResponse,\n type MediaPlaybackPolicy,\n type ReadAloudData,\n type ReadAloudLearnerResponse,\n type ReadAloudWordAlignment,\n type RecordingRef,\n type ScoringDetail,\n type SpeechAssessment,\n validateActivity,\n xAPIBuilder,\n xapiDefinitionFor,\n} from '@intellectif/lk-core';\nimport {\n type CSSProperties,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useActivityState } from '../../hooks/useActivityState.js';\nimport type { RecordedTake, SpeechRecorderStatus } from '../../hooks/useSpeechRecorder.js';\nimport { useSpeechRecorder } from '../../hooks/useSpeechRecorder.js';\nimport { localeDirectionOf } from '../../i18n/direction.js';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStrings } from '../../i18n/strings.js';\nimport { ANONYMOUS_ACTOR, isDevelopment, objectIdFor } from '../_internal.js';\nimport { PronunciationFeedback } from '../PronunciationFeedback/index.js';\nimport { markSentence, VISUALLY_HIDDEN } from '../PronunciationFeedback/PronunciationFeedback.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport { joinCaptureGroup } from '../shared/capture-registry.js';\nimport { useDeliveryPolicy } from '../shared/delivery.js';\nimport { FeedbackRegion } from '../shared/FeedbackRegion.js';\nimport { usePlaybackRefusal } from '../shared/playback-refusal.js';\nimport {\n fractionOfGrade,\n type OutcomeReading,\n percentOfGrade,\n type ReadGrade,\n readAssessResult,\n readEvidence,\n readOutcome,\n} from '../shared/read-grade.js';\nimport {\n mintTake,\n SequenceSlotContext,\n stampTake,\n type TakeState,\n} from '../shared/sequence-slot.js';\nimport type { ActivityProps, Renderable } from '../types.js';\nimport { readRecordingBounds, secondsWithin } from './recording-bounds.js';\n\n/**\n * Names playback groups for the model recordings. Per mount and never derived\n * from `useId`: two separately hydrated roots derive the same id for the same\n * tree position, and their recordings would pause each other. The name never\n * reaches the DOM.\n */\nlet modelsGroupCount = 0;\n\n/** A title with something in it a screen reader can say. */\nconst VISIBLE_TEXT_RE = /[^\\p{White_Space}\\p{Cc}\\p{Cf}\\p{Default_Ignorable_Code_Point}]/u;\n\n/** A stored mark's id, as `gradeReadAloud` numbers the reference words. */\nconst MARK_ID_RE = /^w\\d+$/;\n\n/** The states a mark rebuilt from stored details can carry: insertions are not stored. */\nconst STORED_STATES: readonly ReadAloudWordAlignment['state'][] = [\n 'correct',\n 'mispronounced',\n 'omitted',\n];\n\n/** The level meter's resolution, in steps the skin can select on. */\nconst LEVEL_STEPS = 10;\n\n/**\n * What an application's assessor came back with.\n *\n * `code` is a plain `string` and not `SpeechUnscorableCode`: an application's\n * own assessor may refuse a take for a reason the SDK has no name for, and a\n * narrower type would make it unreportable. This component classifies the two\n * codes that mean \"nothing was heard\" and treats every other one the same way.\n */\nexport type ReadAloudAssessResult =\n | { status: 'graded'; assessment: SpeechAssessment; grade: GradeRecord }\n | { status: 'unscorable'; code: string; assessment?: SpeechAssessment }\n | { status: 'failed'; retryable: boolean };\n\n/**\n * How a take reaches the application, and how a judgement comes back.\n *\n * Named `recordingBinding` rather than `recording` because both\n * `ReadAloudData.recording` (the bounds) and `ReadAloudLearnerResponse.recording`\n * (the stored take) already exist: the plain name would read as one of those.\n */\nexport interface RecordingBinding {\n /**\n * Puts the take in your storage and returns its key. **Required** outside\n * `review`: without it a learner can speak into a control that submits\n * nothing. Settle it — a promise that never does leaves the submit pending\n * for ever.\n */\n upload(take: RecordedTake): Promise<RecordingRef>;\n /**\n * Judges the stored take. `practice` only — an exam never assesses on the\n * client. Optional: a binding without it records, uploads and submits, and\n * the feedback is replaced by a notice.\n */\n assess?(ref: RecordingRef): Promise<ReadAloudAssessResult>;\n /** A playable link to a stored take. `review` only. */\n playbackUrl?(ref: RecordingRef): Promise<string>;\n}\n\nexport interface ReadAloudProps extends ActivityProps<ReadAloudData> {\n /** How a take is stored and judged. See {@link RecordingBinding}. */\n recordingBinding?: RecordingBinding;\n /**\n * The evidence behind a stored grade, for `review`. Without it a graded\n * outcome still shows its marks, rebuilt from `outcome.grade.details`; with\n * neither, the score is shown and no marks are.\n */\n assessment?: SpeechAssessment;\n /**\n * Confidence above which a break note is shown, 0..1, forwarded to\n * {@link PronunciationFeedback}. No default — see its own prop.\n */\n breakThreshold?: number;\n /** The same, for the monotone note. */\n monotoneThreshold?: number;\n /**\n * The URL of a self-hosted copy of `CAPTURE_PROCESSOR_SOURCE`, handed to the\n * recorder, for a Content-Security-Policy whose `script-src` does not allow\n * `blob:`. Without it the capture module is loaded from a `blob:` URL, and a\n * policy that refuses one sends every take through the deprecated\n * main-thread `ScriptProcessorNode` — a take that still records, so nothing\n * says so. See `useSpeechRecorder`'s option of the same name.\n */\n workletUrl?: string;\n /**\n * Never called. A read-aloud answer is a key in your storage, and there is\n * none until the take has been uploaded — so there is no intermediate\n * response to report, and reporting the take itself would offer a host a\n * recording it has nowhere to put. `onSubmit` carries the response, once.\n */\n onChange?: (response: LearnerResponse) => void;\n}\n\n/** What the live region says: the SDK's own sentence, then any grader feedback. */\ninterface Announcement {\n text: string;\n feedback: string | null;\n}\n\n/**\n * How far the submitted take has got. `idle` also means \"nothing submitted yet\".\n *\n * Every other phase names its `take`, the number `mintTake` gave the submit\n * that reached it: a phase describes one take, and a pager told about it must\n * be able to tell that take from the one before it.\n */\ntype SubmitPhase =\n | { kind: 'idle' }\n | { kind: 'uploading'; take: number }\n | { kind: 'upload-failed'; take: number }\n | { kind: 'assessing'; take: number }\n | { kind: 'submitted'; take: number }\n | { kind: 'unassessed'; take: number }\n /**\n * The assessor's judgement, as `readAssessResult` read it: `grade` is `null`\n * when what came back cannot be shown as a grade, and `assessment` is `null`\n * when there is no evidence to mark words from.\n */\n | {\n kind: 'graded';\n take: number;\n assessment: SpeechAssessment | null;\n grade: ReadGrade | null;\n }\n | { kind: 'unscorable'; take: number; code: string | undefined }\n | { kind: 'assess-failed'; take: number; retryable: boolean };\n\n/**\n * Whether the screen offers \"Try again\" for the take this phase describes —\n * the one answer to that question, read by the button that offers it and by\n * the take state a pager is told, so the two cannot disagree. A retry re-sends\n * the SAME take: a take that failed to upload is sent again, and a stored take\n * whose assessment failed is judged again without being uploaded twice.\n */\nfunction offersRetry(phase: SubmitPhase, uploaded: RecordingRef | null): boolean {\n return (\n phase.kind === 'upload-failed' ||\n (phase.kind === 'assess-failed' && phase.retryable && uploaded !== null)\n );\n}\n\n/**\n * The state of the phase's take, as a pager deciding whether its set is\n * finished reads it. Derived from the phase alone, so no path through a submit\n * can leave a pager waiting on a take this component has finished with: every\n * path ends in a phase.\n */\nfunction takeStateOf(phase: SubmitPhase, uploaded: RecordingRef | null): TakeState {\n if (phase.kind === 'uploading' || phase.kind === 'assessing') {\n return 'in-flight';\n }\n return offersRetry(phase, uploaded) ? 'retryable' : 'settled';\n}\n\n/**\n * Whether the recorder is capturing, or about to: the one answer to \"is a take\n * being recorded\", read by everything that must hold still for one. A\n * permission prompt counts — a prompt answered after the pane went away would\n * otherwise open a microphone nobody is in front of, and a model recording\n * played while the prompt is up would be charged a play and then silenced.\n */\nfunction capturing(status: SpeechRecorderStatus): boolean {\n return status === 'recording' || status === 'requesting-permission';\n}\n\n/**\n * What a take is a take OF: the item, the words, their language and the bounds\n * the take is captured under. A change to any of these is a different reading,\n * and a take of the old one answers nothing.\n *\n * Deliberately NOT the identity of `data`. `redact()` returns a new object on\n * every call, and `activities={raw.map(redact)}` is a documented way to pass a\n * paper, so a host that rebuilds its items on every render is ordinary — and a\n * reset keyed on identity silently threw away every grade still in flight and\n * refunded the take budget on each of those renders, so a `maxTakes: 1` exam\n * accepted a second take. The title, the instructions and the model recordings\n * are presentation; editing them leaves the take a take of the same reading.\n *\n * The bounds are the CLAMPED ones, which are what the recorder is given. Read\n * field by field into strings, so no value unvalidated data carries can make\n * the key throw.\n */\nfunction readingKeyOf(\n data: Renderable<ReadAloudData>,\n bounds: { maxSeconds: number; minSeconds: number | undefined; maxTakes: number | undefined },\n): string {\n const text = (value: unknown): string | null => (typeof value === 'string' ? value : null);\n return JSON.stringify([\n text(data.id),\n text(data.referenceText),\n text(data.locale),\n String(bounds.maxSeconds),\n String(bounds.minSeconds),\n String(bounds.maxTakes),\n ]);\n}\n\n/**\n * The slower model recording as an `ActivityMedia`: it follows the model\n * recording's playback policy minus `maxPlays` (`controls`, `seek`, `rate`,\n * the native hints), so a locked scrubber or a fixed speed binds both files —\n * and it never carries a budget of its own, so `<ActivityMedia>` never demands\n * a binding for it. `undefined` when there is no slow recording.\n */\nfunction slowMediaFor(\n data: Renderable<ReadAloudData>,\n label: string,\n): ActivityMediaData | undefined {\n const slow = data.slowMedia;\n if (slow === undefined) {\n return undefined;\n }\n const policy: MediaPlaybackPolicy = data.media?.playback ?? {};\n const { maxPlays: _budget, ...inherited } = policy;\n const hasPolicy = Object.values(inherited).some((value) => value !== undefined);\n return {\n type: 'audio',\n url: slow.url,\n alt: slow.alt ?? label,\n ...(hasPolicy ? { playback: inherited } : {}),\n };\n}\n\n/**\n * Word marks rebuilt from stored scoring details — a server-recorded mark shown\n * after the fact, with no assessment on the client. Inserted words are not in\n * the details (`gradeReadAloud` stores one mark per REFERENCE word, and a word\n * the learner added is not one the item asked for), so none appear.\n *\n * The details are a read grade's, so every entry is well-formed; what is left\n * to decide here is whether they are word marks at all, rather than a rubric's\n * own breakdown.\n */\nfunction marksFromDetails(details: readonly ScoringDetail[]): ReadAloudWordAlignment[] | null {\n if (details.length === 0 || !details.every((detail) => MARK_ID_RE.test(detail.itemId))) {\n return null;\n }\n return details.map((detail) => {\n const heard = Array.isArray(detail.learnerResponse)\n ? detail.learnerResponse.join(' ')\n : detail.learnerResponse;\n const reference = Array.isArray(detail.correctResponse)\n ? detail.correctResponse.join(' ')\n : detail.correctResponse;\n const correct = detail.outcome !== undefined ? detail.outcome === 'correct' : detail.correct;\n const state: ReadAloudWordAlignment['state'] = correct\n ? 'correct'\n : detail.outcome === 'incorrect-omission'\n ? 'omitted'\n : 'mispronounced';\n return {\n itemId: detail.itemId,\n reference,\n heard,\n state,\n // `ScoringDetail.score` is the word's accuracy divided by 100; the marks\n // carry it back in the units the assessment used.\n ...(typeof detail.score === 'number' ? { accuracy: detail.score * 100 } : {}),\n };\n });\n}\n\n/** The stored take a learner response points at, or `null` for a blank or another type. */\nfunction recordingOf(response: LearnerResponse | undefined): RecordingRef | null {\n return response !== undefined && response.type === 'read-aloud' ? response.recording : null;\n}\n\n/** The response this component emits; `takes` is written only when one was made. */\nfunction responseOf(recording: RecordingRef | null, takes: number): ReadAloudLearnerResponse {\n return { type: 'read-aloud', recording, ...(takes > 0 ? { takes } : {}) };\n}\n\n/**\n * Which learner message an unscorable code earns. The two codes that mean\n * \"nothing was heard\" send the learner back to a quieter room; every other one,\n * the SDK's and an application's own alike — and no code at all — says only\n * that this take could not be assessed. **The code itself is never shown** — it\n * is a developer's label.\n */\nfunction unscorableMessage(code: string | undefined, s: LkStrings): string {\n return code === 'no_speech' || code === 'insufficient_voiced_time'\n ? s.readAloudNotHeard\n : s.readAloudNotAssessed;\n}\n\n/**\n * The announcement, with grader feedback in its own isolated span: its\n * direction is its own, so an English \"Well read.\" in an Arabic interface keeps\n * its full stop at its end. Its language is not guessed — feedback may be\n * written in the interface's language or the reading's.\n */\nfunction AnnouncementText({ announcement }: { announcement: Announcement | null }) {\n if (announcement === null) {\n return null;\n }\n return (\n <>\n {announcement.text}\n {announcement.feedback ? (\n <>\n {' '}\n <span dir=\"auto\">{announcement.feedback}</span>\n </>\n ) : null}\n </>\n );\n}\n\n/**\n * The server's outcome in `review`, as `readOutcome` read it. Nothing on this\n * path scores, infers or defaults a grade.\n *\n * `scoreShown` says whether the grade is already on screen — it is, whenever\n * `<PronunciationFeedback>` was given the same grade — so the sentence is not\n * rendered twice. The wrapper and its attributes are rendered either way,\n * because they are what a stylesheet and a diagnostic read.\n */\nfunction OutcomeSummary({\n reading,\n s,\n scoreShown,\n}: {\n reading: OutcomeReading;\n s: LkStrings;\n scoreShown: boolean;\n}) {\n switch (reading.kind) {\n case 'graded':\n return (\n <div\n className=\"lk-ra-outcome\"\n data-status={reading.status}\n data-passed={String(reading.grade.passed)}\n >\n {scoreShown ? null : (\n <>\n <p className=\"lk-ra-grade\">\n {s.scoreAnnouncement(percentOfGrade(reading.grade), reading.grade.passed)}\n </p>\n {reading.grade.feedback ? (\n <p className=\"lk-ra-grade-feedback\" dir=\"auto\">\n {reading.grade.feedback}\n </p>\n ) : null}\n </>\n )}\n </div>\n );\n case 'deferred':\n return (\n <div className=\"lk-ra-outcome\" data-status=\"deferred\">\n <p className=\"lk-ra-grade\">{s.awaitingGrade}</p>\n </div>\n );\n case 'unscorable':\n // An unscorable code is a developer-facing label: it goes on the element\n // for diagnostics and routing, and never in front of the learner as text.\n return (\n <div\n className=\"lk-ra-outcome\"\n data-status=\"unscorable\"\n {...(reading.code !== undefined ? { 'data-code': reading.code } : {})}\n >\n <p className=\"lk-ra-grade\">{s.couldNotBeGraded}</p>\n </div>\n );\n default:\n // A stored outcome that claims a grade and carries none that can be read\n // gets the deferred vocabulary's own sentence rather than \"Score NaN%.\":\n // there is a grade, and this screen cannot say what it was. No\n // `data-passed`, because there is no verdict to style.\n return (\n <div\n className=\"lk-ra-outcome\"\n {...(reading.status !== undefined ? { 'data-status': reading.status } : {})}\n >\n <p className=\"lk-ra-grade\">{s.couldNotBeGraded}</p>\n </div>\n );\n }\n}\n\n/**\n * Reads a text aloud and hands the recording to the application.\n *\n * The SDK records; it never judges. A take is captured as mono 16-bit PCM WAV\n * at 16 kHz — the one format `inspectWav` measures directly — handed to\n * `recordingBinding.upload`, and reported through `onSubmit`. Whether anything\n * assesses it, and where, is the application's: in `practice` an optional\n * `assess` may come back with a grade, and in `exam` nothing is ever assessed\n * on the client.\n *\n * **`recordingBinding.upload` is required outside `review`** and its absence\n * throws at render, in production too: a learner must never speak into a\n * control that stores nothing. `assess` is not required — a binding without it\n * records, uploads and submits, and shows a notice in place of the feedback.\n *\n * **A grade that comes back is read, not trusted** — whether `assess` returned\n * it or `outcome` carries it. A score that is not a number between 0 and its\n * maximum, or a `passed` that is not a boolean, is shown as could-not-be-graded\n * and never reaches `onComplete` or a statement; `onComplete` fires exactly when\n * a score is on screen, with that score.\n *\n * **Under a strict Content-Security-Policy** the take is played back from a\n * `blob:` URL, so `media-src` must allow `blob:` — refused, the player and the\n * per-word buttons give way to a note saying the take cannot be played here —\n * and the capture module loads from one too, so either `script-src` allows\n * `blob:` or `workletUrl` names a self-hosted copy of `CAPTURE_PROCESSOR_SOURCE`.\n *\n * ```tsx\n * <ReadAloud\n * data={item}\n * recordingBinding={{ upload, assess }}\n * onSubmit={persist}\n * onComplete={record}\n * />\n * ```\n */\nexport function ReadAloud({\n data,\n onComplete,\n onSubmit,\n onInteraction,\n value,\n defaultValue,\n defaultSubmitted,\n renderMode = 'practice',\n outcome,\n mediaBudget,\n mediaStrings,\n strings,\n theme,\n locale,\n disabled,\n recordingBinding,\n assessment,\n breakThreshold,\n monotoneThreshold,\n workletUrl,\n delivery,\n}: ReadAloudProps) {\n const isExam = renderMode === 'exam';\n // A read-aloud's grade comes back from the host's assessor, but showing it is\n // still feedback: without it the learner hears that the take was handed in,\n // and the grade still reaches `onComplete`. It has no solution and no hints.\n const policy = useDeliveryPolicy(delivery);\n const isReview = renderMode === 'review';\n const s = useLkStrings(strings);\n // The pager slot this component sits in, if any: the capture group its pane\n // stops, and where the state of each take is reported. A context and never a\n // prop, so none of it is on this component's published signature — a\n // consumer string in a prop could stop another pager's microphone. `null`\n // for a standalone component, which then belongs to no group at all and\n // releases the microphone from its own unmount alone.\n const slot = useContext(SequenceSlotContext);\n const captureGroup = slot?.captureGroup;\n\n const devError = useMemo(() => {\n if (!isDevelopment()) {\n return null;\n }\n // No `hasKey` branch, unlike `<Dictation>`: a read-aloud item has no answer\n // key, so `redact()` produces a projection the strict redacted schema\n // describes exactly and there is no revealed variant to make room for.\n if (data.redacted === true) {\n try {\n assertRedacted(data);\n return null;\n } catch (error) {\n return error instanceof Error ? error : new Error(String(error));\n }\n }\n const result = validateActivity('read-aloud', data);\n return result.success ? null : new ActivitySchemaError('read-aloud', result.errors);\n }, [data]);\n\n // The bounds a take is captured under, READ and not merely defaulted, because\n // `practice` renders unvalidated content in production: a `maxSeconds` of\n // 100000 arms a recording that never stops itself, a `maxTakes` of \"1\" meant\n // unlimited takes, and a minimum above the maximum refused every take. One\n // reader owns all of it — see `readRecordingBounds`.\n const { maxSeconds, minSeconds, maxTakes } = readRecordingBounds(data.recording);\n const readingKey = readingKeyOf(data, { maxSeconds, minSeconds, maxTakes });\n\n const recorder = useSpeechRecorder(\n useMemo(\n () => ({\n maxDurationMs: maxSeconds * 1000,\n ...(minSeconds !== undefined ? { minDurationMs: minSeconds * 1000 } : {}),\n ...(maxTakes !== undefined ? { maxTakes } : {}),\n // Forwarded, never defaulted: without it the recorder mints its own\n // `blob:` module, which is the one a strict policy refuses.\n ...(workletUrl !== undefined ? { workletUrl } : {}),\n }),\n [maxSeconds, minSeconds, maxTakes, workletUrl],\n ),\n );\n\n const { state, start, complete, getTimeSpent, reset } = useActivityState(\n defaultSubmitted === true ? 'completed' : 'idle',\n );\n const [phase, setPhase] = useState<SubmitPhase>({ kind: 'idle' });\n const [uploaded, setUploaded] = useState<RecordingRef | null>(null);\n const [takeUrl, setTakeUrl] = useState<string | null>(null);\n const [playbackUrl, setPlaybackUrl] = useState<string | null>(null);\n // Re-keys the alert so the SAME message announces again — a learner who is\n // refused the microphone twice hears it twice.\n const [errorNonce, setErrorNonce] = useState(0);\n\n const ids = useId();\n const titleId = `${ids}-title`;\n // Per mount, not per activity id: two renderings of one item on a page (a\n // review list, a preview beside a sequence) are separate players.\n const [modelsGroup] = useState(() => {\n modelsGroupCount += 1;\n return `lk-ra-models-${modelsGroupCount}`;\n });\n\n // The latest callbacks, so an effect can fire an event without depending on a\n // binding or a handler the host rebuilds on every render.\n const interactionRef = useRef(onInteraction);\n interactionRef.current = onInteraction;\n const bindingRef = useRef(recordingBinding);\n bindingRef.current = recordingBinding;\n // Guards a second submit while one is in flight: two clicks would upload the\n // same take twice and charge the application for two assessments.\n const submittingRef = useRef(false);\n // Which reading this instance is on. Everything asynchronous reads it once\n // and re-reads it after every await, so an instance that has been unmounted,\n // or handed a different reading, reports nothing for the one it has left —\n // the treatment the playback-url effect below already gives itself.\n //\n // A counter rather than the boolean `live` that effect uses, because a\n // boolean cleared on the reading reset would stay cleared and leave a\n // perfectly live component unable to submit its next take.\n const runRef = useRef(0);\n // The pager slot as of the latest render, and the last take it was told\n // about, so an unmount can tell it that take is over — a take whose result\n // this instance will now never report must not hold a set open for ever.\n const slotRef = useRef(slot);\n slotRef.current = slot;\n const reportedTakeRef = useRef<number | null>(null);\n useEffect(\n () => () => {\n runRef.current += 1;\n const take = reportedTakeRef.current;\n if (take !== null) {\n slotRef.current?.takeState(take, 'settled');\n }\n },\n [],\n );\n\n const fire = useCallback(\n (type: InteractionKind, payload: Record<string, unknown>): void => {\n interactionRef.current?.({ type, activityId: data.id, timestamp: Date.now(), payload });\n },\n [data.id],\n );\n\n // `recording-started` and `recording-stopped` are reported from the recorder's\n // own status rather than from the click handlers: a take also ends when it\n // reaches its bound, and a handler would report only the ends a learner asked\n // for.\n const lastStatusRef = useRef<SpeechRecorderStatus>(recorder.status);\n useEffect(() => {\n if (lastStatusRef.current === recorder.status) {\n return;\n }\n lastStatusRef.current = recorder.status;\n if (recorder.status === 'recording') {\n fire('recording-started', { takes: recorder.takesUsed, durationMs: 0 });\n } else if (recorder.status === 'recorded' && recorder.take !== null) {\n fire('recording-stopped', {\n takes: recorder.takesUsed,\n durationMs: recorder.take.durationMs,\n });\n }\n }, [recorder.status, recorder.take, recorder.takesUsed, fire]);\n\n useEffect(() => {\n if (recorder.error !== null) {\n setErrorNonce((nonce) => nonce + 1);\n }\n }, [recorder.error]);\n\n // The take preview. Guarded because an embedder may forbid object URLs, and a\n // preview is worth less than the take it previews.\n const take = recorder.take;\n useEffect(() => {\n if (take === null || typeof URL.createObjectURL !== 'function') {\n return;\n }\n const url = URL.createObjectURL(take.blob);\n setTakeUrl(url);\n return () => {\n URL.revokeObjectURL(url);\n setTakeUrl(null);\n };\n }, [take]);\n\n const restored = recordingOf(value ?? defaultValue);\n const restoredKey = restored?.key ?? null;\n // Keyed by the reading as well as the take: the reading reset below clears\n // the link, and a link nothing mints again would leave the stored take with\n // no player for as long as it stays on screen.\n // biome-ignore lint/correctness/useExhaustiveDependencies: keyed by the stored take and the reading, not by the identity of a response object a host rebuilds every render\n useEffect(() => {\n const binding = bindingRef.current;\n if (!isReview || restored === null || binding?.playbackUrl === undefined) {\n return;\n }\n let live = true;\n void Promise.resolve(binding.playbackUrl(restored)).then(\n (url) => {\n if (live) {\n setPlaybackUrl(url);\n }\n },\n () => {\n /* a link that could not be minted; the marks still render without it */\n },\n );\n return () => {\n live = false;\n };\n }, [isReview, restoredKey, readingKey]);\n\n // What the registry may do to this component, and it is narrow: release the\n // DEVICE, and only while one is actually open.\n //\n // The pager stops a hidden slot's captures on EVERY hidden transition, so a\n // join that discarded unconditionally threw away a take that was already\n // encoded — and `discard` deliberately refunds nothing, so Next then Prev\n // left the learner an activity they could no longer answer and no message\n // saying why. That contradicts the pager's own founding principle: slots stay\n // mounted precisely so nobody silently loses an answer. A capture still\n // running has nothing to lose yet; a finished take has everything.\n //\n // The status is read from a ref so this callback keeps ONE identity and the\n // effect below joins the group exactly once, as `playback-group`'s does.\n const statusRef = useRef(recorder.status);\n statusRef.current = recorder.status;\n const releaseMicrophone = useCallback(() => {\n if (capturing(statusRef.current)) {\n recorder.discard();\n }\n }, [recorder.discard]);\n\n // The registry SUPPLEMENTS the recorder's own unmount cleanup; it covers the\n // one case that cleanup cannot, a component left mounted and merely hidden.\n useEffect(() => {\n if (captureGroup === undefined) {\n return;\n }\n return joinCaptureGroup(captureGroup, releaseMicrophone);\n }, [captureGroup, releaseMicrophone]);\n\n // Identity-guarded so the mount run is a no-op: without it this fires after\n // the first paint and undoes every seed it was just given.\n const defaultSubmittedRef = useRef(defaultSubmitted);\n useEffect(() => {\n defaultSubmittedRef.current = defaultSubmitted;\n }, [defaultSubmitted]);\n // Keyed by the READING, never by the identity of `data` — see `readingKeyOf`\n // for the regression that identity caused. A host that rebuilds an\n // unchanged item keeps the take, the budget and any grade still in flight.\n const lastReadingRef = useRef(readingKey);\n useEffect(() => {\n if (lastReadingRef.current === readingKey) {\n return;\n }\n lastReadingRef.current = readingKey;\n submittingRef.current = false;\n // Anything still in flight belongs to the reading that has just left.\n runRef.current += 1;\n setPhase({ kind: 'idle' });\n setUploaded(null);\n setPlaybackUrl(null);\n // `reset`, not `discard`: the takes belong to the READING, and `discard`\n // refunds none of them on purpose. A new reading that inherited the old\n // one's spent takes renders a recorder that looks live and refuses to\n // record — reachable in `<ActivityPreview>`, whose remount key is derived\n // from the media alone, so an author who tries one take and then edits the\n // reference text can never record again without reloading the page.\n recorder.reset();\n reset(defaultSubmittedRef.current === true ? 'completed' : 'idle');\n }, [readingKey, reset, recorder.reset]);\n\n // What became of each take, told to the pager slot this sits in: the one\n // channel through which a sequence learns whether its set can be reported.\n //\n // Read from the committed PHASE, and only from it. Every way a submit can end\n // — a grade, a take with no grade, a failure, a retry, a learner recording\n // again, a reading reset — ends in a phase, so none of them can leave a set\n // waiting on a take this component is done with. And an effect runs after\n // the commit, which is after `onSubmit` and `onComplete` were called for the\n // same phase: a pager told a take has settled has already been handed\n // everything that take produced. The one state that must not wait for a\n // commit — a take on its way — is reported as the submit starts.\n const phaseTake = phase.kind === 'idle' ? null : phase.take;\n const phaseTakeState = takeStateOf(phase, uploaded);\n useEffect(() => {\n // An idle phase settles the take before it: re-recording drops a take\n // whose failure was being offered a retry, and so does a reading reset.\n const take = phaseTake ?? reportedTakeRef.current;\n if (slot === null || take === null) {\n return;\n }\n reportedTakeRef.current = take;\n slot.takeState(take, phaseTakeState);\n }, [slot, phaseTake, phaseTakeState]);\n\n // Model recordings stay silent for as long as the learner records: a\n // microphone that hears the model reading would assess the model's\n // pronunciation, not theirs.\n //\n // Pausing once, when the capture starts, is not enough — the play controls\n // are still on screen and still work throughout the take, so the hazard\n // simply moves one door along. The listener holds the guard for the whole\n // capture and is removed with it; `play` does not bubble, so it is taken in\n // the capture phase. It is what silences the browser's own control bar; a\n // budgeted model is refused before it is charged, by the `disabled` its\n // player is rendered with below.\n const modelsRef = useRef<HTMLDivElement>(null);\n const isCapturing = capturing(recorder.status);\n useEffect(() => {\n if (!isCapturing) {\n return;\n }\n const node = modelsRef.current;\n if (node === null) {\n return;\n }\n const hush = (): void => {\n for (const media of node.querySelectorAll('audio, video')) {\n const element = media as HTMLMediaElement;\n if (!element.paused) {\n element.pause();\n }\n }\n };\n hush();\n node.addEventListener('play', hush, true);\n return () => {\n node.removeEventListener('play', hush, true);\n };\n }, [isCapturing]);\n\n const slowMedia = useMemo(\n () => slowMediaFor(data, s.readAloudSlowRecording),\n [data, s.readAloudSlowRecording],\n );\n\n // The learner's own take, as every element that plays it plays it: the\n // preview before it is sent, the stored link a review mints, and the\n // per-word buttons in the feedback panel. One refusal covers all of them.\n const takeAudio = isReview ? playbackUrl : takeUrl;\n const playback = usePlaybackRefusal(takeAudio);\n // Where focus goes when a retry takes the \"Try again\" button away: the\n // submit it retries, which says it is busy for as long as the retry runs.\n const submitRef = useRef<HTMLButtonElement>(null);\n\n // All hooks are called before these throws, so hook order stays stable.\n if (devError) {\n throw devError;\n }\n // `review` needs no upload: it renders what a server already stored.\n if (!isReview && recordingBinding?.upload === undefined) {\n throw new Error(\n `ReadAloud \"${data.id}\" was rendered in renderMode \"${renderMode}\" without ` +\n '`recordingBinding.upload`. The learner could record a take that is stored ' +\n 'nowhere and submitted as nothing, which looks like a working activity the ' +\n 'whole way through. Pass a `recordingBinding` whose `upload` puts the take in ' +\n 'your storage and returns its key. `assess` is optional: without it the take is ' +\n 'still recorded, uploaded and submitted, and a notice replaces the feedback.',\n );\n }\n\n const submitted = state === 'completed';\n const inactive = disabled === true || submitted || isReview;\n const busy = phase.kind === 'uploading' || phase.kind === 'assessing';\n const takesLeft = maxTakes === undefined ? undefined : Math.max(0, maxTakes - recorder.takesUsed);\n\n /** Marks a failure so the alert announces even when the sentence repeats. */\n const raise = (next: SubmitPhase): void => {\n setErrorNonce((nonce) => nonce + 1);\n setPhase(next);\n };\n\n /**\n * Tells the pager slot, if there is one, that `take` is on its way — before\n * anything is awaited. The report a pager's set waits on must exist before\n * the first result it waits for can arrive; the take's later states follow\n * from the phase it reaches (see the effect above).\n */\n const beginTakeReport = (take: number): void => {\n reportedTakeRef.current = take;\n slot?.takeState(take, 'in-flight');\n };\n\n const runAssess = async (\n ref: RecordingRef,\n current: RecordedTake | null,\n take: number,\n ): Promise<void> => {\n const assess = bindingRef.current?.assess;\n const durationMs = current?.durationMs ?? 0;\n const takes = recorder.takesUsed;\n const run = runRef.current;\n if (assess === undefined) {\n // D5: a binding that stores but does not judge is a supported binding,\n // not a mistake — it is what an authoring preview supplies.\n setPhase({ kind: 'unassessed', take });\n return;\n }\n setPhase({ kind: 'assessing', take });\n fire('assessment-requested', { takes, durationMs });\n let answer: unknown;\n try {\n answer = await assess(ref);\n } catch {\n // Detached while the assessor was working: an instance that has been\n // unmounted, or handed a different reading, reports nothing for the one\n // it has left — not a phase, not an event, and not a grade.\n if (run !== runRef.current) {\n return;\n }\n raise({ kind: 'assess-failed', take, retryable: true });\n fire('assessment-failed', { takes, durationMs, retryable: true });\n return;\n }\n if (run !== runRef.current) {\n return;\n }\n // Read once, here, and nothing below touches `answer` again: the phase the\n // screen renders from, `onComplete` and the statement all take what the\n // reader returned, so they cannot disagree about what came back.\n const result = readAssessResult(answer);\n if (result.status === 'failed') {\n raise({ kind: 'assess-failed', take, retryable: result.retryable });\n fire('assessment-failed', { takes, durationMs, retryable: result.retryable });\n return;\n }\n if (result.status === 'unscorable') {\n // Reported as a failed assessment because that is what it is to a\n // consumer's log — a take that produced no grade — and `code` says which\n // kind. `retryable` is true because another take is offered.\n setPhase({ kind: 'unscorable', take, code: result.code });\n fire('assessment-failed', {\n takes,\n durationMs,\n retryable: true,\n ...(result.code !== undefined ? { code: result.code } : {}),\n });\n return;\n }\n const timeSpent = getTimeSpent();\n setPhase({ kind: 'graded', take, assessment: result.assessment, grade: result.grade });\n const grade = result.grade;\n if (grade === null) {\n // A judgement that carries no grade the reader can show is not an\n // `ActivityResult`: reporting one would mean inventing the score, exactly\n // as an `unscorable` result would. To a consumer's log it is what an\n // unscorable take is — an assessment that produced no grade, with another\n // take offered.\n fire('assessment-failed', { takes, durationMs, retryable: true });\n return;\n }\n\n // xAPI's `score.scaled` is a fraction in [-1, 1], and the SDK's builder\n // writes `ScoringResult.score` into it verbatim, then validates — so a\n // points-based grade (82 out of 100, which `GradeRecord` allows and a\n // grader's own units readily produce) does not merely ship a wrong number:\n // the build THROWS, and the grade never reaches the screen at all. The read\n // grade's fraction is in 0..1 by construction, and `passed` is a boolean by\n // construction, so the statement validates whatever the host sent. The\n // learner's own numbers go to `onComplete` untouched.\n //\n // Stamped with its take, as the response was: a pager records a grade only\n // for the take its slot holds now, so a grade that outlived a newer take\n // can never be filed beside that newer take's response.\n onComplete?.(\n stampTake(\n {\n score: grade.score,\n maxScore: grade.maxScore,\n passed: grade.passed,\n timeSpent,\n xapiStatement: xAPIBuilder.buildAnsweredStatement({\n actor: ANONYMOUS_ACTOR,\n object: {\n id: objectIdFor(data.id),\n name: { [data.locale ?? 'en-US']: data.title },\n ...xapiDefinitionFor(data),\n },\n timeSpentMs: timeSpent,\n response: result.recognizedText ?? '',\n // `ScoringResult.details` is required where `GradeRecord.details` is\n // optional, and an empty list is the honest reading of \"no per-word\n // marks were stored\" — never a mark of its own.\n scoringResult: {\n score: fractionOfGrade(grade),\n maxScore: 1,\n passed: grade.passed,\n feedback: grade.feedback,\n details: grade.details ?? [],\n },\n }),\n },\n take,\n ),\n );\n };\n\n const submitTake = async (current: RecordedTake | null): Promise<void> => {\n const binding = bindingRef.current;\n if (inactive || submittingRef.current || binding === undefined) {\n return;\n }\n submittingRef.current = true;\n const takes = recorder.takesUsed;\n const run = runRef.current;\n // Every submit is a take of its own, a retried upload included: a number\n // no earlier submit on this page had, so whatever an earlier one still\n // brings back cannot be taken for this one's.\n const take = mintTake();\n beginTakeReport(take);\n try {\n let ref: RecordingRef | null = null;\n if (current !== null) {\n setPhase({ kind: 'uploading', take });\n try {\n ref = await binding.upload(current);\n } catch {\n if (run !== runRef.current) {\n return;\n }\n // The take never reached storage, so nothing is submitted: a take\n // that failed to upload must not become a blank response.\n raise({ kind: 'upload-failed', take });\n fire('recording-upload-failed', {\n takes,\n durationMs: current.durationMs,\n retryable: true,\n });\n return;\n }\n // Detached while the take travelled. The bytes are in the application's\n // storage and this instance drops their key, which is a smaller cost\n // than the alternative: `onSubmit` carries no activity id, so a\n // response reported for a reading the learner has left is one a pager\n // would file against whatever is on screen now.\n if (run !== runRef.current) {\n return;\n }\n setUploaded(ref);\n fire('recording-uploaded', { takes, durationMs: current.durationMs });\n }\n onSubmit?.(stampTake(responseOf(ref, takes), take));\n fire('submitted', { takes, durationMs: current?.durationMs ?? 0 });\n if (isExam || ref === null) {\n // Exam never assesses, never scores, never reveals, and builds no xAPI\n // statement: its `correctResponsesPattern` would be the answer key, and\n // a read-aloud is graded on a server from the stored bytes.\n complete();\n setPhase({ kind: 'submitted', take });\n return;\n }\n await runAssess(ref, current, take);\n } finally {\n submittingRef.current = false;\n }\n };\n\n /**\n * Judges the take that is already stored, again — the SAME take, so the same\n * number. Guarded like a submit: an assessment costs an application money,\n * and two presses of one retry must not buy two of them.\n */\n const retryAssess = async (ref: RecordingRef, take: number): Promise<void> => {\n if (submittingRef.current) {\n return;\n }\n submittingRef.current = true;\n beginTakeReport(take);\n try {\n await runAssess(ref, recorder.take, take);\n } finally {\n submittingRef.current = false;\n }\n };\n\n const handleSubmit = (event: React.FormEvent): void => {\n event.preventDefault();\n if (inactive || busy || recorder.take === null || phase.kind !== 'idle') {\n return;\n }\n void submitTake(recorder.take);\n };\n\n const beginTake = (): void => {\n if (inactive || busy || !recorder.canRecord) {\n return;\n }\n if (recorder.take !== null) {\n fire('recording-discarded', {\n takes: recorder.takesUsed,\n durationMs: recorder.take.durationMs,\n });\n }\n setPhase({ kind: 'idle' });\n setUploaded(null);\n start();\n void recorder.start();\n };\n\n // The reading's own language and the direction its tag names. No `_`→`-`\n // normalisation, unlike `<Dictation>`: `ReadAloudData.locale` is canonical by\n // schema, so there is nothing to normalise.\n const contentLang = typeof data.locale === 'string' ? data.locale : undefined;\n const contentDir = localeDirectionOf(data.locale);\n\n // What the host sent, as the one reader of a host's grade reads it. Every\n // path below that shows or decides anything about a grade — the summary, the\n // panel, the marks, who renders the score, the announcement — reads these and\n // never `outcome` or `assessment` directly.\n const outcomeReading = useMemo(() => readOutcome(outcome), [outcome]);\n const keptEvidence = useMemo(() => readEvidence(assessment), [assessment]);\n const storedGrade =\n outcomeReading?.kind === 'graded' && outcomeReading.status === 'graded'\n ? outcomeReading.grade\n : null;\n // The marks a review shows: the assessment when the caller kept one, and the\n // stored details otherwise. With neither, the score is shown and no marks are.\n const storedMarks = useMemo(\n () =>\n policy.feedback && isReview && keptEvidence === null && storedGrade?.details !== undefined\n ? marksFromDetails(storedGrade.details)\n : null,\n [policy.feedback, isReview, keptEvidence, storedGrade],\n );\n // No panel at all without feedback: the marks, the score and the grader's\n // words are all in it.\n const feedbackAssessment = !policy.feedback\n ? null\n : isReview\n ? keptEvidence\n : phase.kind === 'graded'\n ? phase.assessment\n : null;\n const feedbackGrade = isReview ? storedGrade : phase.kind === 'graded' ? phase.grade : null;\n // A take the page refused to play is handed to no per-word button: each of\n // them would do nothing when pressed, and the note in the player's place\n // already says why.\n const feedbackAudio = playback.refused ? null : takeAudio;\n\n // Whether the score and the grader's words are already on screen inside the\n // feedback panel, which is what decides who renders them here.\n //\n // The props say the panel WAS GIVEN a grade; only the DOM says it rendered\n // one. The panel can fail outright — D2's dev throw for evidence the\n // validator refuses is one way, and nothing on either path validates a host's\n // grade either — and clipping on the props alone then hides a score that\n // exists nowhere, leaving a learner \"this activity could not be displayed\"\n // and no percentage at all. So the props may only ever clip for the single\n // commit before the fact arrives, and the fact may always un-clip: a grade on\n // screen twice is a far smaller defect than a grade nowhere. `.lk-pf-grade`\n // is the panel's own hook for this, named as such in its source.\n const rootRef = useRef<HTMLFormElement>(null);\n const [panelLostTheGrade, setPanelLostTheGrade] = useState(false);\n // Only a READ grade is handed to the panel, and the panel shows a read grade's\n // score without exception — so the prediction is made from the reader's\n // output, and never from a grade the panel would refuse.\n const panelHasGrade = feedbackAssessment !== null && feedbackGrade !== null;\n const gradeShownInPanel = panelHasGrade && !panelLostTheGrade;\n // Deliberately without a dependency list: what it reads is the committed DOM,\n // which no list can name. The updater keeps the settled value a fixed point,\n // so a render that changes nothing here costs one extra pass and then stops.\n useEffect(() => {\n const root = rootRef.current;\n const lost = panelHasGrade && (root === null || root.querySelector('.lk-pf-grade') === null);\n setPanelLostTheGrade((was) => (was === lost ? was : lost));\n });\n\n const announcement = useMemo<Announcement | null>(() => {\n if (isReview) {\n return null;\n }\n // The two moments of a capture, said once each. The per-second counter used\n // to be a live region of its own, so a ten-second take produced ten\n // announcements — and on any device without headphones a screen reader\n // speaks each of them into the take about to be assessed FOR PRONUNCIATION.\n // This component already stops the model recordings for exactly that\n // reason; the counter it generates itself is the same hazard.\n if (recorder.status === 'recording') {\n return { text: s.readAloudRecordingStarted, feedback: null };\n }\n // The length is in the sentence because the counter is no longer announced:\n // a take that ran to its bound reads back as \"20 of 20 seconds\", which is\n // how a learner hears that it stopped itself rather than that they stopped it.\n const stopped: Announcement | null =\n recorder.status === 'recorded' && take !== null\n ? {\n text: s.readAloudRecordingStopped(\n secondsWithin(take.durationMs, maxSeconds, 'nearest'),\n maxSeconds,\n ),\n feedback: null,\n }\n : null;\n if (isExam) {\n if (phase.kind === 'submitted' || submitted) {\n return { text: s.answerSubmitted, feedback: null };\n }\n return phase.kind === 'idle' ? stopped : null;\n }\n switch (phase.kind) {\n case 'graded': {\n const grade = phase.grade;\n // A judgement with no grade that can be read says so, in the same\n // sentence a review uses — never nothing, which reads as a grade\n // still on its way.\n if (grade === null) {\n return { text: `${s.answerSubmitted} ${s.couldNotBeGraded}`, feedback: null };\n }\n // Graded, and said to be handed in only: the policy shows no grades.\n if (!policy.feedback) {\n return { text: s.answerSubmitted, feedback: null };\n }\n return {\n text: `${s.answerSubmitted} ${s.scoreAnnouncement(percentOfGrade(grade), grade.passed)}`,\n feedback: grade.feedback,\n };\n }\n case 'unscorable':\n return { text: unscorableMessage(phase.code, s), feedback: null };\n case 'unassessed':\n return { text: s.readAloudAssessmentUnavailable, feedback: null };\n case 'idle':\n return stopped;\n default:\n // Uploading and assessing have their own pending sentence, and both\n // failures have an alert; a stale \"recording stopped\" beside either\n // would be the loudest thing on the screen.\n return null;\n }\n }, [isReview, isExam, phase, submitted, s, recorder.status, take, maxSeconds, policy.feedback]);\n\n const alertText =\n recorder.error !== null\n ? s.readAloudRecorderError(recorder.error)\n : phase.kind === 'upload-failed'\n ? s.readAloudUploadFailed\n : phase.kind === 'assess-failed'\n ? s.readAloudAssessmentFailed\n : null;\n\n return (\n <form\n className=\"lk-ra\"\n ref={rootRef}\n // A title with nothing to say would name the form with silence.\n aria-labelledby={VISIBLE_TEXT_RE.test(data.title) ? titleId : undefined}\n lang={locale}\n data-render-mode={renderMode}\n style={theme as CSSProperties | undefined}\n onSubmit={handleSubmit}\n >\n {/*\n The form's name: read from here, it keeps the content's language. A\n paragraph of its own, so without a language to go by its own first\n letter decides its direction.\n */}\n <p id={titleId} className=\"lk-ra-title\" dir={contentDir ?? 'auto'} lang={contentLang}>\n {data.title}\n </p>\n\n {typeof data.instructions === 'string' && data.instructions !== '' ? (\n <p className=\"lk-ra-instructions\" dir={contentDir ?? 'auto'} lang={contentLang}>\n {data.instructions}\n </p>\n ) : null}\n\n {/* The reading itself. Never redacted: a read-aloud has no answer key, so\n the learner's copy carries the text in full. */}\n <p className=\"lk-ra-reference\" dir={contentDir ?? 'auto'} lang={contentLang}>\n {data.referenceText}\n </p>\n\n {data.media !== undefined || slowMedia !== undefined ? (\n <div className=\"lk-ra-models\" ref={modelsRef}>\n {/*\n Each model recording is a named group (a fieldset with a legend,\n which is what `role=\"group\"` plus a label spells natively), so the\n two players are told apart by name and not only by position.\n */}\n {data.media !== undefined ? (\n <fieldset className=\"lk-ra-model\">\n <legend className=\"lk-ra-model-label\">{s.readAloudModelRecording}</legend>\n {/* The ONLY budgeted recording: the binding is whatever the pager or the caller passes.\n Disabled while a take is captured, so a press is refused BEFORE a play is\n charged — silenced after the charge, it cost the learner a play they never\n heard. Nothing is announced for the refusal: a screen reader speaking now\n would speak into the take. */}\n <ActivityMedia\n media={data.media}\n renderMode={renderMode}\n playbackGroup={modelsGroup}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(locale !== undefined ? { locale } : {})}\n {...(disabled === true || isCapturing ? { disabled: true } : {})}\n />\n </fieldset>\n ) : null}\n {slowMedia !== undefined ? (\n <fieldset className=\"lk-ra-model\" data-slow=\"true\">\n <legend className=\"lk-ra-model-label\">{s.readAloudSlowRecording}</legend>\n {/* No budget of its own, by contract — so no binding is passed and none is demanded. */}\n <ActivityMedia\n media={slowMedia}\n renderMode={renderMode}\n playbackGroup={modelsGroup}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(locale !== undefined ? { locale } : {})}\n {...(disabled === true || isCapturing ? { disabled: true } : {})}\n />\n </fieldset>\n ) : null}\n </div>\n ) : null}\n\n {/* Gone once the item is committed, rather than left behind disabled: a\n submitted exam answer is not a control a learner should still meet. */}\n {isReview || submitted ? null : (\n <div\n className=\"lk-ra-recorder\"\n data-status={recorder.status}\n data-level={\n recorder.status === 'recording'\n ? Math.min(LEVEL_STEPS, Math.round(recorder.level * LEVEL_STEPS))\n : undefined\n }\n >\n {recorder.status === 'recording' ? (\n <button\n type=\"button\"\n className=\"lk-ra-stop\"\n disabled={inactive}\n onClick={() => recorder.stop()}\n >\n {s.readAloudStop}\n </button>\n ) : (\n // aria-disabled, never disabled, once the takes are spent: a\n // natively disabled button drops keyboard focus to the page body,\n // and this is the control a learner comes back to.\n <button\n type=\"button\"\n className=\"lk-ra-record\"\n disabled={inactive}\n aria-disabled={!recorder.canRecord || busy || undefined}\n onClick={beginTake}\n >\n {recorder.take === null ? s.readAloudRecord : s.readAloudRerecord}\n </button>\n )}\n {recorder.status === 'recording' ? (\n // Visible, and hidden from assistive technology, beside the level\n // meter the recorder's `data-level` drives. Its text changes every\n // whole second, so as a live region it announced ten times in a\n // ten-second take — into the microphone. What a screen-reader\n // learner needs instead is said once each, below.\n <p className=\"lk-ra-progress\" aria-hidden=\"true\">\n {s.readAloudRecordingProgress(\n secondsWithin(recorder.elapsedMs, maxSeconds, 'down'),\n maxSeconds,\n )}\n </p>\n ) : null}\n {takesLeft !== undefined && maxTakes !== undefined ? (\n <p className=\"lk-ra-takes\">{s.readAloudTakesRemaining(takesLeft, maxTakes)}</p>\n ) : null}\n </div>\n )}\n\n {takeAudio === null ? null : playback.refused ? (\n // In the player's place, not beside it: a player the page will not\n // load looks exactly like one that works until it is pressed, and then\n // does nothing. A note rather than an alert — the learner can still\n // submit, and nothing here asks them to act.\n <p className=\"lk-ra-take-unavailable\" role=\"note\">\n {s.readAloudPlaybackUnavailable}\n </p>\n ) : (\n // biome-ignore lint/a11y/useMediaCaption: the learner's own take has no caption track to offer; its accessible name says whose recording it is\n <audio\n className=\"lk-ra-take\"\n controls\n src={takeAudio}\n aria-label={s.readAloudYourRecording}\n onError={(event) => playback.refuse(event.currentTarget)}\n {...(isReview ? {} : { 'data-take': recorder.takesUsed })}\n />\n )}\n\n {isReview || submitted ? null : (\n <div className=\"lk-ra-actions\">\n <button\n ref={submitRef}\n type=\"submit\"\n className=\"lk-ra-submit\"\n disabled={disabled === true}\n aria-disabled={recorder.take === null || phase.kind !== 'idle' || undefined}\n aria-busy={busy || undefined}\n >\n {s.submit}\n </button>\n {isExam ? (\n // Explicit, and only here: an exam answer with no recording is a\n // decision a learner takes, never something a failed upload does\n // for them.\n <button\n type=\"button\"\n className=\"lk-ra-submit-blank\"\n disabled={inactive}\n aria-disabled={busy || undefined}\n onClick={() => {\n if (!busy) {\n void submitTake(null);\n }\n }}\n >\n {s.readAloudSubmitWithoutRecording}\n </button>\n ) : null}\n {offersRetry(phase, uploaded) ? (\n <button\n type=\"button\"\n className=\"lk-ra-retry\"\n onClick={() => {\n // The retry takes this button away with the phase it answers,\n // and focus on a removed element drops to the page body — a\n // keyboard learner is then back at the top of the page with no\n // idea the retry started. It goes to the submit being retried.\n submitRef.current?.focus();\n if (phase.kind === 'upload-failed') {\n void submitTake(recorder.take);\n } else if (phase.kind === 'assess-failed' && uploaded !== null) {\n void retryAssess(uploaded, phase.take);\n }\n }}\n >\n {s.readAloudTryAgain}\n </button>\n ) : null}\n </div>\n )}\n\n {busy ? (\n <p className=\"lk-ra-pending\" role=\"status\" aria-busy=\"true\">\n {phase.kind === 'uploading' ? s.readAloudUploading : s.readAloudAssessing}\n </p>\n ) : null}\n\n {alertText !== null ? (\n <p key={errorNonce} className=\"lk-ra-alert\" role=\"alert\">\n {alertText}\n </p>\n ) : null}\n\n {feedbackAssessment !== null ? (\n <PronunciationFeedback\n data={{ referenceText: data.referenceText, locale: data.locale }}\n assessment={feedbackAssessment}\n {...(feedbackGrade !== null ? { grade: feedbackGrade } : {})}\n {...(feedbackAudio !== null ? { audioUrl: feedbackAudio } : {})}\n {...(breakThreshold !== undefined ? { breakThreshold } : {})}\n {...(monotoneThreshold !== undefined ? { monotoneThreshold } : {})}\n {...(locale !== undefined ? { locale } : {})}\n {...(strings !== undefined ? { strings } : {})}\n />\n ) : null}\n\n {storedMarks !== null ? (\n <div className=\"lk-ra-marks\">\n {/* The list's name and the hidden sentences are interface text; only\n the words carry the reading's language. */}\n <ol className=\"lk-ra-words\" aria-label={s.pronunciationWordsLabel} dir={contentDir}>\n {storedMarks.map((entry, index) => {\n const sentence = markSentence(entry, s);\n return (\n <li\n // biome-ignore lint/suspicious/noArrayIndexKey: mark order is the identity; words repeat\n key={index}\n className=\"lk-ra-word\"\n data-state={entry.state}\n >\n {/* One channel for assistive technology, not two: the same\n sentence as a `title` is an accessible description, read\n straight after the name it repeats. */}\n <span className=\"lk-visually-hidden\" style={VISUALLY_HIDDEN}>\n {sentence}\n </span>\n <span\n className=\"lk-ra-word-text\"\n aria-hidden=\"true\"\n dir={contentDir ?? 'auto'}\n lang={contentLang}\n >\n {entry.reference}\n </span>\n </li>\n );\n })}\n </ol>\n {/* Three rows, not four: a stored mark set carries no insertions. */}\n <ul className=\"lk-ra-legend\">\n {STORED_STATES.map((state) => (\n <li key={state} data-state={state}>\n {s.pronunciationLegend(state)}\n </li>\n ))}\n </ul>\n </div>\n ) : null}\n\n <FeedbackRegion id={`${data.id}-feedback`}>\n {isReview ? (\n // A grade read back is feedback; \"not graded yet\" and a grade that\n // could not be read are not.\n outcomeReading !== undefined && (policy.feedback || outcomeReading.kind !== 'graded') ? (\n <OutcomeSummary reading={outcomeReading} s={s} scoreShown={gradeShownInPanel} />\n ) : null\n ) : gradeShownInPanel ? (\n // Announced, not repeated. A grade the feedback panel is already\n // showing would otherwise be read on screen twice, feedback and all —\n // but a live region is the only thing that TELLS a screen-reader\n // learner a grade arrived, since nothing takes focus. Clipped rather\n // than `display: none`, which would stop it being announced at all.\n <span className=\"lk-visually-hidden\" style={VISUALLY_HIDDEN}>\n <AnnouncementText announcement={announcement} />\n </span>\n ) : (\n <AnnouncementText announcement={announcement} />\n )}\n </FeedbackRegion>\n </form>\n );\n}\n","'use client';\n\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport { ActivityErrorBoundary } from '../ActivityErrorBoundary.js';\nimport { ReadAloud as ReadAloudCore, type ReadAloudProps } from './ReadAloud.js';\n\n// `RecordedTake` travels with `RecordingBinding` because `upload`, the one\n// method a binding must have, takes one: a binding written against this subpath\n// must not need a second import path to name its own argument.\nexport type { RecordedTake } from '../../hooks/useSpeechRecorder.js';\nexport type { ReadAloudAssessResult, ReadAloudProps, RecordingBinding } from './ReadAloud.js';\n\n/**\n * Public Read Aloud activity: the core wrapped in `ActivityErrorBoundary` so a\n * render failure (including the dev schema-validation throw and the\n * missing-`upload` guard) degrades to an accessible fallback instead of\n * crashing the host.\n */\nexport function ReadAloud(props: ReadAloudProps) {\n const strings = useLkStrings(props.strings);\n return (\n <ActivityErrorBoundary activityTitle={props.data?.title} strings={strings}>\n <ReadAloudCore {...props} />\n </ActivityErrorBoundary>\n );\n}\n"]}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { useAiExplanation, useAiHints } from './chunk-
|
|
2
|
+
import { useAiExplanation, useAiHints } from './chunk-AQIAQHJC.js';
|
|
3
3
|
import { useRef, useEffect } from 'react';
|
|
4
4
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
function situation(input) {
|
|
7
|
-
const { data, response, submitted, renderMode, ai, locale, onInteraction } = input;
|
|
7
|
+
const { data, response, submitted, renderMode, ai, locale, onInteraction, delivery } = input;
|
|
8
8
|
return {
|
|
9
9
|
data,
|
|
10
10
|
response,
|
|
11
11
|
submitted,
|
|
12
12
|
renderMode,
|
|
13
|
+
delivery,
|
|
13
14
|
...ai !== void 0 ? { ai } : {},
|
|
14
15
|
...locale !== void 0 ? { locale } : {},
|
|
15
16
|
...onInteraction !== void 0 ? { onInteraction } : {}
|
|
@@ -110,5 +111,5 @@ function AiHints({ disabled, ...rest }) {
|
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
export { AiExplanation, AiHints };
|
|
113
|
-
//# sourceMappingURL=chunk-
|
|
114
|
-
//# sourceMappingURL=chunk-
|
|
114
|
+
//# sourceMappingURL=chunk-RUS42NMN.js.map
|
|
115
|
+
//# sourceMappingURL=chunk-RUS42NMN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/shared/AiHelp.tsx"],"names":[],"mappings":"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","file":"chunk-RUS42NMN.js","sourcesContent":["'use client';\n\nimport type {\n InteractionEvent,\n ItemOutcome,\n LearnerResponse,\n ResolvedDeliveryPolicy,\n} from '@intellectif/lk-core';\nimport { useEffect, useRef } from 'react';\nimport type { LearnerAi } from '../../ai/LkAiProvider.js';\nimport { useAiExplanation, useAiHints } from '../../ai/useAiHelp.js';\nimport type { LkStrings } from '../../i18n/strings.js';\nimport type { RenderableActivity, RenderMode } from '../types.js';\n\ninterface Situation {\n ai: LearnerAi | undefined;\n data: RenderableActivity;\n renderMode: RenderMode;\n submitted: boolean;\n /** The learner's answer as it stands: submitted, restored, or being written. */\n response: LearnerResponse;\n /** The component's `locale`, the default language for what is written. */\n locale: string | undefined;\n onInteraction: ((event: InteractionEvent) => void) | undefined;\n strings: LkStrings;\n /** The policy the component resolved, the paper's included. */\n delivery: ResolvedDeliveryPolicy;\n}\n\n/** What the hooks take, from what a component holds: the optional half, without `undefined` values. */\nfunction situation(input: Situation): {\n data: RenderableActivity;\n response: LearnerResponse;\n submitted: boolean;\n renderMode: RenderMode;\n ai?: LearnerAi;\n locale?: string;\n onInteraction?: (event: InteractionEvent) => void;\n delivery: ResolvedDeliveryPolicy;\n} {\n const { data, response, submitted, renderMode, ai, locale, onInteraction, delivery } = input;\n return {\n data,\n response,\n submitted,\n renderMode,\n delivery,\n ...(ai !== undefined ? { ai } : {}),\n ...(locale !== undefined ? { locale } : {}),\n ...(onInteraction !== undefined ? { onInteraction } : {}),\n };\n}\n\n/**\n * \"Explain my answer\": the SDK's own surface over {@link useAiExplanation} — a\n * button, the explanation with who wrote it, and \"not available\" when a call\n * fails or its answer is refused.\n *\n * Nothing renders without a port, or where the rules say no explanation.\n */\nexport function AiExplanation({\n outcome,\n ...rest\n}: Situation & { outcome: ItemOutcome | undefined }) {\n const s = rest.strings;\n const help = useAiExplanation({\n ...situation(rest),\n ...(outcome !== undefined ? { outcome } : {}),\n });\n const buttonRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLElement>(null);\n const refocus = useRef(false);\n\n // The button that asked goes away when the explanation arrives; focus moves\n // to what it asked for rather than falling to the page.\n useEffect(() => {\n if (help.status === 'shown' && refocus.current) {\n refocus.current = false;\n panelRef.current?.focus();\n }\n if (help.status === 'unavailable') {\n refocus.current = false;\n }\n }, [help.status]);\n\n if (!help.offered) {\n return null;\n }\n\n const ask = (): void => {\n refocus.current =\n typeof document !== 'undefined' && document.activeElement === buttonRef.current;\n help.ask();\n };\n\n const loading = help.status === 'loading';\n return (\n <div className=\"lk-ai\" data-state={help.status}>\n {help.explanation === null ? (\n <button\n ref={buttonRef}\n type=\"button\"\n className=\"lk-ai-button\"\n aria-busy={loading || undefined}\n aria-disabled={loading || undefined}\n onClick={ask}\n >\n {loading ? s.aiExplaining : s.aiExplain}\n </button>\n ) : null}\n <div aria-live=\"polite\">\n {help.explanation !== null ? (\n <section\n ref={panelRef}\n className=\"lk-ai-panel\"\n tabIndex={-1}\n aria-label={s.aiExplanation}\n >\n <p className=\"lk-ai-heading\">{s.aiExplanation}</p>\n <p className=\"lk-ai-text\">{help.explanation.text}</p>\n <p className=\"lk-ai-notice\">{s.aiNotice}</p>\n </section>\n ) : help.status === 'unavailable' ? (\n <p className=\"lk-ai-unavailable\">{s.aiExplanationUnavailable}</p>\n ) : null}\n </div>\n </div>\n );\n}\n\n/**\n * \"Get a hint\": the SDK's own surface over {@link useAiHints} — a button, the\n * hints given so far as a numbered list, and who wrote them.\n *\n * The hints stay listed after submit, so the learner can see what they were\n * given, but no more can be asked for. Another question starts with none.\n */\nexport function AiHints({ disabled, ...rest }: Situation & { disabled: boolean }) {\n const s = rest.strings;\n const help = useAiHints({ ...situation(rest), disabled });\n const used = help.used;\n\n if (rest.ai?.hint === undefined || (!help.offered && used === 0)) {\n return null;\n }\n\n const loading = help.status === 'loading';\n return (\n <div className=\"lk-ai lk-ai-hint-area\" data-state={help.status}>\n <div aria-live=\"polite\">\n {used > 0 ? (\n <ol className=\"lk-ai-hints\" aria-label={s.aiHints}>\n {help.hints.map((hint, index) => (\n // Hints are only ever appended, so a position is a stable key.\n // biome-ignore lint/suspicious/noArrayIndexKey: an append-only list\n <li key={index} className=\"lk-ai-hint\">\n <span className=\"lk-ai-hint-label\">{s.aiHintNumber(index + 1)}</span>{' '}\n <span className=\"lk-ai-text\">{hint.text}</span>\n </li>\n ))}\n </ol>\n ) : null}\n {help.status === 'unavailable' ? (\n <p className=\"lk-ai-unavailable\">{s.aiHintUnavailable}</p>\n ) : null}\n </div>\n {help.offered ? (\n used < help.limit ? (\n <button\n type=\"button\"\n className=\"lk-ai-button\"\n aria-busy={loading || undefined}\n aria-disabled={loading || undefined}\n onClick={help.ask}\n >\n {loading ? s.aiHintLoading : s.aiHint}\n </button>\n ) : (\n <p className=\"lk-ai-note\">{s.aiNoMoreHints}</p>\n )\n ) : null}\n {used > 0 ? <p className=\"lk-ai-notice\">{s.aiNotice}</p> : null}\n </div>\n );\n}\n"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var chunkZDYNAWDZ_cjs = require('./chunk-ZDYNAWDZ.cjs');
|
|
5
|
-
var
|
|
5
|
+
var chunkBJLQKJZX_cjs = require('./chunk-BJLQKJZX.cjs');
|
|
6
6
|
var chunkEANGKAKG_cjs = require('./chunk-EANGKAKG.cjs');
|
|
7
7
|
var lkCore = require('@intellectif/lk-core');
|
|
8
8
|
var react = require('react');
|
|
@@ -15,10 +15,15 @@ function hintLimit(ai) {
|
|
|
15
15
|
}
|
|
16
16
|
function hintOffered(input) {
|
|
17
17
|
const { data, renderMode, submitted, disabled } = input;
|
|
18
|
-
|
|
18
|
+
const delivery = input.delivery ?? lkCore.OPEN_DELIVERY_POLICY;
|
|
19
|
+
return delivery.hints && delivery.ai.hints && renderMode === "practice" && !submitted && !disabled && data.redacted !== true && lkCore.aiSupports(data.type, "hint") && lkCore.aiAllowedByContent(data, "hint");
|
|
19
20
|
}
|
|
20
21
|
function explanationOffered(input) {
|
|
21
22
|
const { data, renderMode, submitted, outcome } = input;
|
|
23
|
+
const delivery = input.delivery ?? lkCore.OPEN_DELIVERY_POLICY;
|
|
24
|
+
if (!delivery.feedback || !delivery.solutions || !delivery.ai.explanations) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
22
27
|
const graded = renderMode === "practice" && submitted || renderMode === "review" && outcome?.status === "scored";
|
|
23
28
|
return graded && lkCore.aiSupports(data.type, "explanation") && lkCore.aiAllowedByContent(data, "explanation");
|
|
24
29
|
}
|
|
@@ -63,14 +68,16 @@ function shownPayload(result) {
|
|
|
63
68
|
}
|
|
64
69
|
function useAiExplanation(input) {
|
|
65
70
|
const { data, response, submitted, outcome } = input;
|
|
66
|
-
const
|
|
71
|
+
const around = react.useContext(chunkBJLQKJZX_cjs.SequenceSlotContext);
|
|
72
|
+
const renderMode = modeInForce(input.renderMode, around?.renderMode);
|
|
73
|
+
const delivery = lkCore.combineDeliveryPolicies(input.delivery, around?.delivery);
|
|
67
74
|
const ai = chunkZDYNAWDZ_cjs.useLearnerAi(input.ai);
|
|
68
75
|
const resetKey = explanationKey(renderMode, submitted, response, outcome);
|
|
69
76
|
const [state, setState] = react.useState({ key: resetKey, status: "idle" });
|
|
70
77
|
const run = react.useRef(0);
|
|
71
78
|
const controller = react.useRef(null);
|
|
72
79
|
const current = state.key === resetKey ? state : { key: resetKey, status: "idle" };
|
|
73
|
-
const offered = ai?.explain !== void 0 && explanationOffered({ data, renderMode, submitted, outcome });
|
|
80
|
+
const offered = ai?.explain !== void 0 && explanationOffered({ data, renderMode, submitted, outcome, delivery });
|
|
74
81
|
const latest = react.useRef({ input, renderMode, ai, resetKey, offered, current });
|
|
75
82
|
latest.current = { input, renderMode, ai, resetKey, offered, current };
|
|
76
83
|
react.useEffect(() => {
|
|
@@ -144,7 +151,9 @@ function useAiExplanation(input) {
|
|
|
144
151
|
}
|
|
145
152
|
function useAiHints(input) {
|
|
146
153
|
const { data, submitted } = input;
|
|
147
|
-
const
|
|
154
|
+
const around = react.useContext(chunkBJLQKJZX_cjs.SequenceSlotContext);
|
|
155
|
+
const renderMode = modeInForce(input.renderMode, around?.renderMode);
|
|
156
|
+
const delivery = lkCore.combineDeliveryPolicies(input.delivery, around?.delivery);
|
|
148
157
|
const disabled = input.disabled === true;
|
|
149
158
|
const ai = chunkZDYNAWDZ_cjs.useLearnerAi(input.ai);
|
|
150
159
|
const resetKey = data.id;
|
|
@@ -152,7 +161,7 @@ function useAiHints(input) {
|
|
|
152
161
|
const run = react.useRef(0);
|
|
153
162
|
const controller = react.useRef(null);
|
|
154
163
|
const current = state.key === resetKey ? state : { key: resetKey, hints: [], status: "idle" };
|
|
155
|
-
const offered = ai?.hint !== void 0 && hintOffered({ data, renderMode, submitted, disabled });
|
|
164
|
+
const offered = ai?.hint !== void 0 && hintOffered({ data, renderMode, submitted, disabled, delivery });
|
|
156
165
|
const limit = hintLimit(ai);
|
|
157
166
|
const latest = react.useRef({ input, renderMode, ai, offered, limit, current });
|
|
158
167
|
latest.current = { input, renderMode, ai, offered, limit, current };
|
|
@@ -249,5 +258,5 @@ function useAiHints(input) {
|
|
|
249
258
|
|
|
250
259
|
exports.useAiExplanation = useAiExplanation;
|
|
251
260
|
exports.useAiHints = useAiHints;
|
|
252
|
-
//# sourceMappingURL=chunk-
|
|
253
|
-
//# sourceMappingURL=chunk-
|
|
261
|
+
//# sourceMappingURL=chunk-TRMWBJWT.cjs.map
|
|
262
|
+
//# sourceMappingURL=chunk-TRMWBJWT.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ai/rules.ts","../src/ai/useAiHelp.ts"],"names":["OPEN_DELIVERY_POLICY","aiSupports","aiAllowedByContent","isDevelopment","useContext","SequenceSlotContext","combineDeliveryPolicies","useLearnerAi","useState","useRef","useEffect","useCallback","aiExplanationRequest","checkAiExplanation","aiHintRequest","checkAiHint"],"mappings":"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","file":"chunk-TRMWBJWT.cjs","sourcesContent":["/**\n * When AI help is offered, and what the SDK does with what a port answers.\n *\n * Internal: these are the rules behind {@link useAiHints} and\n * {@link useAiExplanation}, which are what a host calls. They live apart from\n * the hooks so the hooks' module exports nothing but the hooks themselves.\n */\nimport {\n type AiRefusal,\n aiAllowedByContent,\n aiSupports,\n type ItemOutcome,\n type LearnerResponse,\n OPEN_DELIVERY_POLICY,\n type ResolvedDeliveryPolicy,\n} from '@intellectif/lk-core';\nimport { isDevelopment } from '../components/_internal.js';\nimport type { RenderableActivity, RenderMode } from '../components/types.js';\nimport type { LearnerAi } from './LkAiProvider.js';\n\n/** The most hints a question gives when the host names no number, and the most it may name. */\nconst DEFAULT_MAX_HINTS = 3;\nconst MAX_HINTS_CEILING = 10;\n\n/** `maxHints` as a whole number from 1 to 10; anything else is the default. */\nexport function hintLimit(ai: LearnerAi | undefined): number {\n const asked = ai?.maxHints;\n return typeof asked === 'number' && Number.isInteger(asked) && asked >= 1\n ? Math.min(asked, MAX_HINTS_CEILING)\n : DEFAULT_MAX_HINTS;\n}\n\n/**\n * Whether the learner may ask for a hint: `practice`, before submit, on a\n * question that is not disabled, of a type that takes hints, whose author left\n * hints on, and whole — a redacted item is an exam's, and gives none.\n */\nexport function hintOffered(input: {\n data: RenderableActivity;\n renderMode: RenderMode;\n submitted: boolean;\n disabled: boolean;\n /** The paper's policy: hints at all, and AI hints in particular. Open when absent. */\n delivery?: ResolvedDeliveryPolicy;\n}): boolean {\n const { data, renderMode, submitted, disabled } = input;\n const delivery = input.delivery ?? OPEN_DELIVERY_POLICY;\n return (\n delivery.hints &&\n delivery.ai.hints &&\n renderMode === 'practice' &&\n !submitted &&\n !disabled &&\n data.redacted !== true &&\n aiSupports(data.type, 'hint') &&\n aiAllowedByContent(data, 'hint')\n );\n}\n\n/**\n * Whether the learner may ask for an explanation: of a graded answer — in\n * `practice` once submitted, or in `review` when the grade of record is\n * scored — of a type the SDK explains, whose author left explanations on.\n * Never in `exam`.\n */\nexport function explanationOffered(input: {\n data: RenderableActivity;\n renderMode: RenderMode;\n submitted: boolean;\n outcome: ItemOutcome | undefined;\n /**\n * The paper's policy. An explanation needs AI explanations on, and more: it\n * explains a grade, so it needs the grade shown (`feedback`), and it all but\n * always says what the right answer was, so it needs `solutions` too. On a\n * paper that hides the answer, \"Explain my answer\" would hand it over.\n */\n delivery?: ResolvedDeliveryPolicy;\n}): boolean {\n const { data, renderMode, submitted, outcome } = input;\n const delivery = input.delivery ?? OPEN_DELIVERY_POLICY;\n if (!delivery.feedback || !delivery.solutions || !delivery.ai.explanations) {\n return false;\n }\n const graded =\n (renderMode === 'practice' && submitted) ||\n (renderMode === 'review' && outcome?.status === 'scored');\n return graded && aiSupports(data.type, 'explanation') && aiAllowedByContent(data, 'explanation');\n}\n\n/** Calls a port, turning a synchronous throw into a rejection. */\nexport function callPort<T>(call: () => Promise<T>): Promise<T> {\n try {\n return Promise.resolve(call());\n } catch (error) {\n return Promise.reject(error);\n }\n}\n\n/** Tells a developer why a port's answer was not shown. Silent in production. */\nexport function warnRefused(\n feature: 'explanation' | 'hint',\n activityId: string,\n reason: AiRefusal,\n): void {\n if (isDevelopment()) {\n console.warn(\n `learning-kit: the AI ${feature} for \"${activityId}\" was not shown (${reason}). The learner saw \"not available\" instead.`,\n );\n }\n}\n\n/** The language to write in: the host's choice for the learner, else the interface language. */\nexport const localeOf = (\n ai: LearnerAi | undefined,\n locale: string | undefined,\n): { learnerLocale?: string } => {\n const chosen = ai?.learnerLocale ?? locale;\n return chosen === undefined ? {} : { learnerLocale: chosen };\n};\n\n/** What an explanation is about: when any of it changes, the explanation shown no longer is. */\nexport function explanationKey(\n renderMode: RenderMode,\n submitted: boolean,\n response: LearnerResponse,\n outcome: ItemOutcome | undefined,\n): string {\n let graded = '';\n if (outcome !== undefined) {\n graded = `${outcome.status}:${'score' in outcome ? outcome.score : ''}`;\n }\n return `${renderMode}|${submitted}|${graded}|${JSON.stringify(response)}`;\n}\n","'use client';\n\nimport {\n type AiTextResult,\n aiExplanationRequest,\n aiHintRequest,\n checkAiExplanation,\n checkAiHint,\n combineDeliveryPolicies,\n type DeliveryPolicy,\n type InteractionEvent,\n type ItemOutcome,\n type LearnerResponse,\n} from '@intellectif/lk-core';\nimport { useCallback, useContext, useEffect, useRef, useState } from 'react';\nimport { SequenceSlotContext } from '../components/shared/sequence-slot.js';\nimport type { RenderableActivity, RenderMode } from '../components/types.js';\nimport { type LearnerAi, useLearnerAi } from './LkAiProvider.js';\nimport {\n callPort,\n explanationKey,\n explanationOffered,\n hintLimit,\n hintOffered,\n localeOf,\n warnRefused,\n} from './rules.js';\n\n/**\n * The question AI help is being asked about, as the SDK's own components know\n * it. A host drawing its own question passes what it has; everything optional\n * defaults to what the components pass.\n */\nexport interface AiHelpSituation {\n /** The question, as `data` is given to any activity component. */\n data: RenderableActivity;\n /** The learner's answer as it stands: submitted, restored, or being written. */\n response: LearnerResponse;\n /** Whether the learner has committed that answer. */\n submitted: boolean;\n /**\n * Pass on the `renderMode` your question was given. Left out, the paper\n * around the question answers for it — a question inside `<ActivitySequence>`\n * or `<InteractiveVideo>` knows its mode — and `practice` only where there is\n * no paper at all. An `exam` around the question wins over anything passed\n * here.\n */\n renderMode?: RenderMode;\n /**\n * The AI ports to use, overriding {@link LkAiProvider}'s for this question.\n * A question inside `<ActivitySequence>` or `<InteractiveVideo>` is handed\n * the ports in force as `ai` — pass that straight in.\n */\n ai?: LearnerAi;\n /** The interface language: the default language for what a model writes. */\n locale?: string;\n /** Where `ai-hint-shown` and `ai-explanation-shown` go. */\n onInteraction?: (event: InteractionEvent) => void;\n /**\n * The delivery policy your question was handed. The paper around the\n * question holds as well — a set that switched AI hints off keeps them off\n * here, whatever is passed — so leaving it out never turns anything on.\n */\n delivery?: DeliveryPolicy | null;\n}\n\nexport interface AiExplanationInput extends AiHelpSituation {\n /** The grade of record, which `review` explains. */\n outcome?: ItemOutcome;\n}\n\nexport interface AiExplanationHelp {\n /**\n * Whether to offer the learner an explanation at all: a port is present, the\n * answer is graded, the type is one the SDK explains, and the author left\n * explanations on. False in `exam`, always.\n */\n offered: boolean;\n status: 'idle' | 'loading' | 'shown' | 'unavailable';\n /** What to show, once it has arrived and been checked. Its `text` is text, never HTML. */\n explanation: AiTextResult | null;\n /**\n * Asks the port, when the learner presses something. Does nothing while a\n * call is on its way, and nothing at all where `offered` is false — so an\n * exam cannot reach a model through this hook, whatever ports are in scope.\n *\n * One identity for the life of the question, so it is safe in an effect's\n * dependencies.\n */\n ask: () => void;\n}\n\nexport interface AiHintsInput extends AiHelpSituation {\n /** A disabled question gives no hints, as it takes no answer. */\n disabled?: boolean;\n}\n\nexport interface AiHintsHelp {\n /**\n * Whether the learner may ask for hints here at all: a port is present, the\n * mode is `practice`, the answer is not in, the question is not disabled or\n * redacted, the type takes hints and the author left them on. It stays true\n * once `used` reaches `limit` — there are no more to give, but the question\n * is still one that gives them.\n */\n offered: boolean;\n status: 'idle' | 'loading' | 'unavailable';\n /** Every hint shown for this question, in the order they were given. */\n hints: readonly AiTextResult[];\n /** The host's `maxHints`, as a whole number from 1 to 10 (3 by default). */\n limit: number;\n /** How many hints have been shown. A refused hint is not counted. */\n used: number;\n /**\n * Asks for the next hint. Does nothing at the limit, while a call is on its\n * way, or where `offered` is false. One identity for the life of the\n * question.\n */\n ask: () => void;\n}\n\n/**\n * The mode a question is answered in: what the caller says, and what the paper\n * around it says.\n *\n * A host drawing its own question is given `renderMode` like every other prop\n * and is expected to pass it on, but nothing makes it — and a hook that took\n * `practice` on trust would offer hints on a paper of record, as soon as an\n * `LkAiProvider` sat anywhere above the pager. So an `exam` around the question\n * wins, and where the caller says nothing the paper answers for it. A question\n * standing on its own belongs to no paper, and is `practice` as before.\n */\nfunction modeInForce(asked: RenderMode | undefined, around: RenderMode | undefined): RenderMode {\n if (around === 'exam') {\n return 'exam';\n }\n return asked ?? around ?? 'practice';\n}\n\n/**\n * What the record of help a learner was shown carries beside the fact of it:\n * who wrote it, and what the call cost — both only where the port sent them.\n *\n * The text is deliberately absent. A host that wants to keep what a learner\n * read has it in its own port; an interaction is a record of what happened, and\n * is kept for every learner and every question.\n */\nfunction shownPayload(result: AiTextResult): Record<string, unknown> {\n return {\n ...(result.provenance !== undefined ? { provenance: result.provenance } : {}),\n ...(result.usage !== undefined ? { usage: result.usage } : {}),\n };\n}\n\ntype ExplanationState =\n | { key: string; status: 'idle' | 'loading' | 'unavailable' }\n | { key: string; status: 'shown'; result: AiTextResult };\n\n/**\n * \"Explain my answer\", without the SDK's own button and panel: the rules, the\n * call, the checks and the record, for a question you draw yourself.\n *\n * It is what `<MultipleChoice>` and the others use, so a host's own question\n * offers help on exactly the same terms: never in `exam`, never where the\n * item's author switched explanations off, and never an explanation whose\n * verdict contradicts the grade the SDK computed.\n *\n * ```tsx\n * const { offered, status, explanation, ask } = useAiExplanation({\n * data, response, submitted, renderMode, outcome, ai: question.ai,\n * });\n * if (!offered) return null;\n * return explanation === null ? (\n * <button type=\"button\" onClick={ask} aria-busy={status === 'loading'}>Explain my answer</button>\n * ) : (\n * <section>\n * <p>{explanation.text}</p>\n * <p>Written by AI. It can make mistakes.</p>\n * </section>\n * );\n * ```\n *\n * **Say who wrote it.** The SDK's own panel carries \"Written by AI. It can make\n * mistakes.\"; a page that drops that line passes a model's words off as the\n * course's.\n *\n * When the answer or its grade changes, the explanation is dropped and a call\n * still on its way is abandoned — a late explanation never lands beside an\n * answer it was not written for — as it is when the caller unmounts.\n */\nexport function useAiExplanation(input: AiExplanationInput): AiExplanationHelp {\n const { data, response, submitted, outcome } = input;\n const around = useContext(SequenceSlotContext);\n const renderMode = modeInForce(input.renderMode, around?.renderMode);\n const delivery = combineDeliveryPolicies(input.delivery, around?.delivery);\n const ai = useLearnerAi(input.ai);\n const resetKey = explanationKey(renderMode, submitted, response, outcome);\n const [state, setState] = useState<ExplanationState>({ key: resetKey, status: 'idle' });\n const run = useRef(0);\n const controller = useRef<AbortController | null>(null);\n const current: ExplanationState =\n state.key === resetKey ? state : { key: resetKey, status: 'idle' };\n const offered =\n ai?.explain !== undefined &&\n explanationOffered({ data, renderMode, submitted, outcome, delivery });\n\n // What `ask` reads, as of this render, so `ask` itself can keep one identity\n // for the life of the question. The state goes in too: an event handler runs\n // after the render that set it, so the ref is what the learner last saw.\n const latest = useRef({ input, renderMode, ai, resetKey, offered, current });\n latest.current = { input, renderMode, ai, resetKey, offered, current };\n\n // Another answer, or the caller going away: abandon the call in flight.\n // biome-ignore lint/correctness/useExhaustiveDependencies: the key is the trigger — its cleanup runs when the answer changes\n useEffect(() => {\n return () => {\n run.current += 1;\n controller.current?.abort();\n controller.current = null;\n };\n }, [resetKey]);\n\n const ask = useCallback((): void => {\n const now = latest.current;\n const port = now.ai?.explain;\n if (port === undefined || !now.offered || now.current.status === 'loading') {\n return;\n }\n const key = now.resetKey;\n const request = aiExplanationRequest({\n data: now.input.data,\n response: now.input.response,\n ...(now.renderMode === 'review' && now.input.outcome !== undefined\n ? { outcome: now.input.outcome }\n : {}),\n ...localeOf(now.ai, now.input.locale),\n });\n if (request === null) {\n setState({ key, status: 'unavailable' });\n return;\n }\n run.current += 1;\n const mine = run.current;\n controller.current?.abort();\n const abort = new AbortController();\n controller.current = abort;\n setState({ key, status: 'loading' });\n callPort(() => port(request, { signal: abort.signal })).then(\n (raw) => {\n if (run.current !== mine) {\n return;\n }\n const checked = checkAiExplanation(raw, request);\n if (!checked.ok) {\n warnRefused('explanation', now.input.data.id, checked.refusal);\n setState({ key, status: 'unavailable' });\n latest.current.input.onInteraction?.({\n type: 'ai-help-refused',\n activityId: now.input.data.id,\n timestamp: Date.now(),\n payload: { feature: 'explanation', reason: checked.refusal },\n });\n return;\n }\n setState({ key, status: 'shown', result: checked.result });\n latest.current.input.onInteraction?.({\n type: 'ai-explanation-shown',\n activityId: now.input.data.id,\n timestamp: Date.now(),\n payload: shownPayload(checked.result),\n });\n },\n () => {\n if (run.current === mine) {\n setState({ key, status: 'unavailable' });\n }\n },\n );\n }, []);\n\n return {\n offered,\n status: current.status,\n explanation: current.status === 'shown' ? current.result : null,\n ask,\n };\n}\n\ninterface HintState {\n key: string;\n hints: AiTextResult[];\n status: 'idle' | 'loading' | 'unavailable';\n}\n\n/**\n * \"Get a hint\", without the SDK's own button and list: the rules, the call, the\n * checks and the record, for a question you draw yourself.\n *\n * It is what `<MultipleChoice>` and the others use, so a host's own question\n * gives hints on exactly the same terms: only in `practice` before submit, up\n * to the host's limit, never on a redacted item or a type the SDK does not hint\n * for, and never a hint that gives the answer away — which is refused rather\n * than shown, and not counted.\n *\n * ```tsx\n * const { offered, status, hints, used, limit, ask } = useAiHints({\n * data, response, submitted, renderMode, ai: question.ai,\n * });\n * ```\n *\n * **Say who wrote them**, as the SDK's own list does: \"Written by AI. It can\n * make mistakes.\"\n *\n * Keep the hints on screen after submit — the learner can see what they were\n * given — while `offered` turns false. Another question starts with none.\n */\nexport function useAiHints(input: AiHintsInput): AiHintsHelp {\n const { data, submitted } = input;\n const around = useContext(SequenceSlotContext);\n const renderMode = modeInForce(input.renderMode, around?.renderMode);\n const delivery = combineDeliveryPolicies(input.delivery, around?.delivery);\n const disabled = input.disabled === true;\n const ai = useLearnerAi(input.ai);\n const resetKey = data.id;\n const [state, setState] = useState<HintState>({ key: resetKey, hints: [], status: 'idle' });\n const run = useRef(0);\n const controller = useRef<AbortController | null>(null);\n const current: HintState =\n state.key === resetKey ? state : { key: resetKey, hints: [], status: 'idle' };\n const offered =\n ai?.hint !== undefined && hintOffered({ data, renderMode, submitted, disabled, delivery });\n const limit = hintLimit(ai);\n\n const latest = useRef({ input, renderMode, ai, offered, limit, current });\n latest.current = { input, renderMode, ai, offered, limit, current };\n\n // Another question, or the caller going away: abandon the call in flight.\n // biome-ignore lint/correctness/useExhaustiveDependencies: the key is the trigger — its cleanup runs when the question changes\n useEffect(() => {\n return () => {\n run.current += 1;\n controller.current?.abort();\n controller.current = null;\n };\n }, [resetKey]);\n\n // Submitted, or disabled, while a hint was on its way: it would arrive for an\n // answer that can no longer use it.\n useEffect(() => {\n if (!offered) {\n run.current += 1;\n controller.current?.abort();\n controller.current = null;\n setState((previous) =>\n previous.status === 'loading' ? { ...previous, status: 'idle' } : previous,\n );\n }\n }, [offered]);\n\n const ask = useCallback((): void => {\n const now = latest.current;\n const port = now.ai?.hint;\n const held = now.current;\n if (\n port === undefined ||\n !now.offered ||\n held.status === 'loading' ||\n held.hints.length >= now.limit\n ) {\n return;\n }\n const key = held.key;\n const request = aiHintRequest({\n data: now.input.data,\n response: now.input.response,\n previousHints: held.hints.map((hint) => hint.text),\n ...localeOf(now.ai, now.input.locale),\n });\n if (request === null) {\n setState({ ...held, status: 'unavailable' });\n return;\n }\n run.current += 1;\n const mine = run.current;\n controller.current?.abort();\n const abort = new AbortController();\n controller.current = abort;\n setState({ ...held, status: 'loading' });\n callPort(() => port(request, { signal: abort.signal })).then(\n (raw) => {\n if (run.current !== mine) {\n return;\n }\n const checked = checkAiHint(raw, request);\n if (!checked.ok) {\n warnRefused('hint', now.input.data.id, checked.refusal);\n setState((shown) => ({ ...shown, status: 'unavailable' }));\n latest.current.input.onInteraction?.({\n type: 'ai-help-refused',\n activityId: now.input.data.id,\n timestamp: Date.now(),\n payload: {\n feature: 'hint',\n reason: checked.refusal,\n hintNumber: request.hintNumber,\n },\n });\n return;\n }\n setState((shown) => ({\n ...shown,\n hints: shown.key === key ? [...shown.hints, checked.result] : shown.hints,\n status: 'idle',\n }));\n latest.current.input.onInteraction?.({\n type: 'ai-hint-shown',\n activityId: now.input.data.id,\n timestamp: Date.now(),\n payload: { hintNumber: request.hintNumber, ...shownPayload(checked.result) },\n });\n },\n () => {\n if (run.current === mine) {\n setState((shown) => ({ ...shown, status: 'unavailable' }));\n }\n },\n );\n }, []);\n\n return {\n offered,\n status: current.status,\n hints: current.hints,\n limit,\n used: current.hints.length,\n ask,\n };\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var chunkIXMT7X3E_cjs = require('./chunk-IXMT7X3E.cjs');
|
|
5
5
|
var chunkAPZ6JFN6_cjs = require('./chunk-APZ6JFN6.cjs');
|
|
6
6
|
var chunkEANGKAKG_cjs = require('./chunk-EANGKAKG.cjs');
|
|
7
7
|
var chunkL7WY5YFQ_cjs = require('./chunk-L7WY5YFQ.cjs');
|
|
@@ -90,9 +90,11 @@ function WrittenResponse({
|
|
|
90
90
|
onInteraction,
|
|
91
91
|
theme,
|
|
92
92
|
locale,
|
|
93
|
-
disabled
|
|
93
|
+
disabled,
|
|
94
|
+
delivery
|
|
94
95
|
}) {
|
|
95
96
|
const s = chunkAHGB5HH6_cjs.useLkStrings(strings);
|
|
97
|
+
const policy = chunkIXMT7X3E_cjs.useDeliveryPolicy(delivery);
|
|
96
98
|
const devError = react.useMemo(() => {
|
|
97
99
|
if (!chunkEANGKAKG_cjs.isDevelopment()) {
|
|
98
100
|
return null;
|
|
@@ -287,7 +289,7 @@ function WrittenResponse({
|
|
|
287
289
|
children: s.submit
|
|
288
290
|
}
|
|
289
291
|
),
|
|
290
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
292
|
+
/* @__PURE__ */ jsxRuntime.jsx(chunkIXMT7X3E_cjs.FeedbackRegion, { id: `${data.id}-feedback`, children: isReview ? outcome && (policy.feedback || !chunkIXMT7X3E_cjs.outcomeShowsMarks(outcome)) ? /* @__PURE__ */ jsxRuntime.jsx(OutcomeSummary, { outcome, s }) : null : summary })
|
|
291
293
|
]
|
|
292
294
|
}
|
|
293
295
|
);
|
|
@@ -298,5 +300,5 @@ function WrittenResponse2(props) {
|
|
|
298
300
|
}
|
|
299
301
|
|
|
300
302
|
exports.WrittenResponse = WrittenResponse2;
|
|
301
|
-
//# sourceMappingURL=chunk-
|
|
302
|
-
//# sourceMappingURL=chunk-
|
|
303
|
+
//# sourceMappingURL=chunk-UHIOVOFK.cjs.map
|
|
304
|
+
//# sourceMappingURL=chunk-UHIOVOFK.cjs.map
|