@intellectif/lk-react 23.0.0 → 23.1.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 +26 -0
- package/README.md +4 -4
- package/dist/{WrittenResponse-CgRrjM4S.d.ts → WrittenResponse-B7A5gHMj.d.ts} +1 -1
- package/dist/{WrittenResponse-D2K1VW_V.d.cts → WrittenResponse-BpL9p2Hn.d.cts} +1 -1
- package/dist/ai/LkAiProvider.cjs +3 -3
- package/dist/ai/LkAiProvider.d.cts +12 -2
- package/dist/ai/LkAiProvider.d.ts +12 -2
- package/dist/ai/LkAiProvider.js +1 -1
- package/dist/ai/useAiHelp.cjs +9 -7
- package/dist/ai/useAiHelp.d.cts +82 -3
- package/dist/ai/useAiHelp.d.ts +82 -3
- package/dist/ai/useAiHelp.js +2 -4
- package/dist/{chunk-VPU6UY4O.cjs → chunk-2PNSVBLQ.cjs} +5 -6
- package/dist/chunk-2PNSVBLQ.cjs.map +1 -0
- package/dist/{chunk-HZIT24BK.js → chunk-4ZZWZSWI.js} +4 -4
- package/dist/{chunk-HZIT24BK.js.map → chunk-4ZZWZSWI.js.map} +1 -1
- package/dist/{chunk-B7JU52YQ.cjs → chunk-7ALBSPYR.cjs} +34 -35
- package/dist/chunk-7ALBSPYR.cjs.map +1 -0
- package/dist/{chunk-TGTP5DKF.js → chunk-7NFY3M3W.js} +10 -10
- package/dist/{chunk-TGTP5DKF.js.map → chunk-7NFY3M3W.js.map} +1 -1
- package/dist/{chunk-E4UF4XZM.js → chunk-7SB2MJA4.js} +4 -4
- package/dist/{chunk-E4UF4XZM.js.map → chunk-7SB2MJA4.js.map} +1 -1
- package/dist/{chunk-ERV666AR.js → chunk-ABJJ3LII.js} +9 -10
- package/dist/chunk-ABJJ3LII.js.map +1 -0
- package/dist/{chunk-NFGUJ52G.cjs → chunk-BIGGXHAW.cjs} +8 -2
- package/dist/chunk-BIGGXHAW.cjs.map +1 -0
- package/dist/{chunk-O7MINPKZ.js → chunk-C7QZE5BI.js} +9 -10
- package/dist/chunk-C7QZE5BI.js.map +1 -0
- package/dist/{chunk-IYGF7PZY.cjs → chunk-CWTUWNV3.cjs} +25 -26
- package/dist/chunk-CWTUWNV3.cjs.map +1 -0
- package/dist/{chunk-4MXECBZT.js → chunk-DC4CQCK5.js} +40 -13
- package/dist/chunk-DC4CQCK5.js.map +1 -0
- package/dist/{chunk-PGAPZ6UE.cjs → chunk-DS6HN3YR.cjs} +34 -35
- package/dist/chunk-DS6HN3YR.cjs.map +1 -0
- package/dist/{chunk-U5VFJVQ2.js → chunk-FDQR4GAZ.js} +3 -4
- package/dist/chunk-FDQR4GAZ.js.map +1 -0
- package/dist/{chunk-DPNCCWOV.js → chunk-FKGMSOJF.js} +8 -9
- package/dist/chunk-FKGMSOJF.js.map +1 -0
- package/dist/{chunk-SPSKDNEE.js → chunk-G6PXJ2SA.js} +11 -12
- package/dist/chunk-G6PXJ2SA.js.map +1 -0
- package/dist/{chunk-C4PSJ3ZC.js → chunk-HNLRVRQR.js} +8 -2
- package/dist/chunk-HNLRVRQR.js.map +1 -0
- package/dist/{chunk-FISEZBLF.cjs → chunk-J3C72RPK.cjs} +7 -7
- package/dist/{chunk-FISEZBLF.cjs.map → chunk-J3C72RPK.cjs.map} +1 -1
- package/dist/{chunk-SHDSOOBF.cjs → chunk-KFLIDTDU.cjs} +25 -26
- package/dist/chunk-KFLIDTDU.cjs.map +1 -0
- package/dist/{chunk-W6UMY7PF.cjs → chunk-KHKH5LCT.cjs} +7 -8
- package/dist/chunk-KHKH5LCT.cjs.map +1 -0
- package/dist/{chunk-7F2YQWMW.cjs → chunk-LBEKRN6Y.cjs} +122 -11
- package/dist/chunk-LBEKRN6Y.cjs.map +1 -0
- package/dist/{chunk-6RIM5EQP.cjs → chunk-LPUWXIPR.cjs} +197 -12
- package/dist/chunk-LPUWXIPR.cjs.map +1 -0
- package/dist/{chunk-XS6OYYVR.cjs → chunk-MKXEM2C5.cjs} +19 -20
- package/dist/chunk-MKXEM2C5.cjs.map +1 -0
- package/dist/{chunk-CXIP7ERK.js → chunk-MXNC5UNO.js} +182 -8
- package/dist/chunk-MXNC5UNO.js.map +1 -0
- package/dist/{chunk-MXZHL4U5.cjs → chunk-NAK55BYU.cjs} +31 -32
- package/dist/chunk-NAK55BYU.cjs.map +1 -0
- package/dist/{chunk-UKUHVCZX.cjs → chunk-NDBMKGQ5.cjs} +5 -5
- package/dist/{chunk-UKUHVCZX.cjs.map → chunk-NDBMKGQ5.cjs.map} +1 -1
- package/dist/{chunk-XYJBRKBZ.js → chunk-ORT3KLGI.js} +3 -4
- package/dist/chunk-ORT3KLGI.js.map +1 -0
- package/dist/{chunk-JMEZYYCE.js → chunk-PWHECW6N.js} +11 -12
- package/dist/chunk-PWHECW6N.js.map +1 -0
- package/dist/{chunk-6GQLCCGN.js → chunk-RBS5A2DF.js} +9 -10
- package/dist/chunk-RBS5A2DF.js.map +1 -0
- package/dist/{chunk-B4JRBV56.cjs → chunk-RPWUDXG5.cjs} +6 -6
- package/dist/{chunk-B4JRBV56.cjs.map → chunk-RPWUDXG5.cjs.map} +1 -1
- package/dist/{chunk-KI6DJ5ZF.js → chunk-TCDJY3H6.js} +9 -10
- package/dist/chunk-TCDJY3H6.js.map +1 -0
- package/dist/{chunk-DZ7VLLDQ.cjs → chunk-TP5UMRAU.cjs} +66 -39
- package/dist/chunk-TP5UMRAU.cjs.map +1 -0
- package/dist/{chunk-HYOUMTPW.js → chunk-TS7J6DTY.js} +3 -3
- package/dist/{chunk-HYOUMTPW.js.map → chunk-TS7J6DTY.js.map} +1 -1
- package/dist/{chunk-OJGTJOWI.cjs → chunk-TUVENMXB.cjs} +2 -2
- package/dist/chunk-TUVENMXB.cjs.map +1 -0
- package/dist/{chunk-WZ2TBXVB.cjs → chunk-V6BA44DS.cjs} +18 -18
- package/dist/{chunk-WZ2TBXVB.cjs.map → chunk-V6BA44DS.cjs.map} +1 -1
- package/dist/{chunk-2OP5YHRR.js → chunk-XO5R73Y4.js} +120 -10
- package/dist/chunk-XO5R73Y4.js.map +1 -0
- package/dist/{chunk-QAZO3ANW.cjs → chunk-YY4PFR3Z.cjs} +34 -35
- package/dist/chunk-YY4PFR3Z.cjs.map +1 -0
- package/dist/{chunk-RX3A6KIY.js → chunk-ZPI6Q42R.js} +2 -2
- package/dist/chunk-ZPI6Q42R.js.map +1 -0
- package/dist/components/ActivityPreview.cjs +16 -18
- package/dist/components/ActivityPreview.d.cts +2 -2
- package/dist/components/ActivityPreview.d.ts +2 -2
- package/dist/components/ActivityPreview.js +15 -17
- package/dist/components/ActivitySequence.cjs +17 -19
- package/dist/components/ActivitySequence.d.cts +2 -2
- package/dist/components/ActivitySequence.d.ts +2 -2
- package/dist/components/ActivitySequence.js +16 -18
- package/dist/components/Dictation.cjs +9 -11
- package/dist/components/Dictation.d.cts +1 -1
- package/dist/components/Dictation.d.ts +1 -1
- package/dist/components/Dictation.js +8 -10
- package/dist/components/FillInTheBlanks.cjs +9 -11
- package/dist/components/FillInTheBlanks.d.cts +1 -1
- package/dist/components/FillInTheBlanks.d.ts +1 -1
- package/dist/components/FillInTheBlanks.js +8 -10
- package/dist/components/GapSelect.cjs +9 -11
- package/dist/components/GapSelect.d.cts +1 -1
- package/dist/components/GapSelect.d.ts +1 -1
- package/dist/components/GapSelect.js +8 -10
- package/dist/components/InteractiveVideo.cjs +16 -18
- package/dist/components/InteractiveVideo.d.cts +2 -2
- package/dist/components/InteractiveVideo.d.ts +2 -2
- package/dist/components/InteractiveVideo.js +14 -16
- package/dist/components/MultipleChoice.cjs +9 -11
- package/dist/components/MultipleChoice.d.cts +1 -1
- package/dist/components/MultipleChoice.d.ts +1 -1
- package/dist/components/MultipleChoice.js +8 -10
- package/dist/components/PronunciationFeedback.cjs +6 -5
- package/dist/components/PronunciationFeedback.d.cts +28 -7
- package/dist/components/PronunciationFeedback.d.ts +28 -7
- package/dist/components/PronunciationFeedback.js +5 -4
- package/dist/components/ReadAloud.cjs +9 -9
- package/dist/components/ReadAloud.d.cts +2 -2
- package/dist/components/ReadAloud.d.ts +2 -2
- package/dist/components/ReadAloud.js +8 -8
- package/dist/components/StimulusPanel.cjs +4 -4
- package/dist/components/StimulusPanel.d.cts +1 -1
- package/dist/components/StimulusPanel.d.ts +1 -1
- package/dist/components/StimulusPanel.js +3 -3
- package/dist/components/WrittenResponse.cjs +8 -10
- package/dist/components/WrittenResponse.d.cts +3 -3
- package/dist/components/WrittenResponse.d.ts +3 -3
- package/dist/components/WrittenResponse.js +7 -9
- package/dist/i18n/LkIntlProvider.cjs +7 -7
- package/dist/i18n/LkIntlProvider.d.cts +2 -2
- package/dist/i18n/LkIntlProvider.d.ts +2 -2
- package/dist/i18n/LkIntlProvider.js +1 -1
- package/dist/index.cjs +58 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +18 -20
- package/dist/index.js.map +1 -1
- package/dist/theme/skin.css +55 -9
- package/dist/{types-CrtIcRcK.d.ts → types-C4IsUQsK.d.ts} +16 -0
- package/dist/{types-CO0XLEN6.d.cts → types-CJgZWtl4.d.cts} +16 -0
- package/package.json +3 -3
- package/dist/chunk-2OP5YHRR.js.map +0 -1
- package/dist/chunk-4MXECBZT.js.map +0 -1
- package/dist/chunk-54DX6SLS.cjs +0 -42
- package/dist/chunk-54DX6SLS.cjs.map +0 -1
- package/dist/chunk-6GQLCCGN.js.map +0 -1
- package/dist/chunk-6RIM5EQP.cjs.map +0 -1
- package/dist/chunk-7F2YQWMW.cjs.map +0 -1
- package/dist/chunk-B7JU52YQ.cjs.map +0 -1
- package/dist/chunk-C4PSJ3ZC.js.map +0 -1
- package/dist/chunk-CXIP7ERK.js.map +0 -1
- package/dist/chunk-DPNCCWOV.js.map +0 -1
- package/dist/chunk-DXFCYNST.js +0 -35
- package/dist/chunk-DXFCYNST.js.map +0 -1
- package/dist/chunk-DZ7VLLDQ.cjs.map +0 -1
- package/dist/chunk-ERV666AR.js.map +0 -1
- package/dist/chunk-IYGF7PZY.cjs.map +0 -1
- package/dist/chunk-J2QIHZ3H.cjs +0 -27
- package/dist/chunk-J2QIHZ3H.cjs.map +0 -1
- package/dist/chunk-JMEZYYCE.js.map +0 -1
- package/dist/chunk-KI6DJ5ZF.js.map +0 -1
- package/dist/chunk-MXZHL4U5.cjs.map +0 -1
- package/dist/chunk-NFGUJ52G.cjs.map +0 -1
- package/dist/chunk-O7MINPKZ.js.map +0 -1
- package/dist/chunk-OJGTJOWI.cjs.map +0 -1
- package/dist/chunk-PGAPZ6UE.cjs.map +0 -1
- package/dist/chunk-QAZO3ANW.cjs.map +0 -1
- package/dist/chunk-RX3A6KIY.js.map +0 -1
- package/dist/chunk-SHDSOOBF.cjs.map +0 -1
- package/dist/chunk-SPSKDNEE.js.map +0 -1
- package/dist/chunk-U4KTOH7C.js +0 -22
- package/dist/chunk-U4KTOH7C.js.map +0 -1
- package/dist/chunk-U5VFJVQ2.js.map +0 -1
- package/dist/chunk-VPU6UY4O.cjs.map +0 -1
- package/dist/chunk-W6UMY7PF.cjs.map +0 -1
- package/dist/chunk-XS6OYYVR.cjs.map +0 -1
- package/dist/chunk-XYJBRKBZ.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# @intellectif/lk-react
|
|
2
2
|
|
|
3
|
+
## 23.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#86](https://github.com/intellectif/learning-kit/pull/86) [`2215060`](https://github.com/intellectif/learning-kit/commit/22150600b0d8f5f058214a0551566b58e154276d) Thanks [@diego28e](https://github.com/diego28e)! - **Coaching on a reading aloud**: "Coach me on this reading" under a read-aloud's marks, where a model may coach only the words the speech engine marked.
|
|
8
|
+
|
|
9
|
+
**Action required:** none unless your code switches over `AiFeature`, `AiRefusal` or `InteractionKind` without a `default` branch (new members `'pronunciation-coaching'`, `'contradicts-marks'` and `'ai-coaching-shown'`), reads `AiCheckCase.request` without narrowing on `feature`, or implements a complete `LkStrings` (six new keys). Nothing changes until you pass a `pronunciationCoaching` port. Install lk-core 1.1.0 with lk-react 23.1.0: lk-react calls the new lk-core functions, and its peer range moves to `^1.1.0`. See https://github.com/intellectif/learning-kit/blob/main/docs/upgrading.md
|
|
10
|
+
|
|
11
|
+
- **lk-core:** `aiCoachingRequest` builds what a model is given: the text, the engine's marks word by word — with each word's sounds and what each was heard as, when the marks come from the assessment — its dimension scores, and the grade. `checkAiCoaching` checks the reply before a learner sees it. Every word it coaches must be one the engine marked mispronounced or omitted, and every sound one the engine reported for that word; anything else refuses the whole reply as `contradicts-marks`. The words come back in reading order, each as the marks show it, and no number in the reply is read. An assessment the grader would not read (no speech, unscripted, another text or locale) gives no coaching.
|
|
12
|
+
- **lk-react:** a `pronunciationCoaching` port on `LearnerAi`. `<ReadAloud>` offers coaching under its marks in `practice` once a take is graded and in `review` wherever marks are shown, from a kept assessment or a stored grade; never in `exam`. `<PronunciationFeedback>` offers it too, given the item's `id` and `title` in `data`, and takes `ai`, `renderMode`, `delivery` and `onInteraction` for it. It follows the author's and the paper's `ai.explanations` and needs `feedback`, not `solutions`. One coaching per take. `useAiCoaching` gives marks you draw yourself the same rules.
|
|
13
|
+
- **Records:** `ai-coaching-shown` (how many words were coached, provenance and usage; never the text), and `ai-help-refused` with `feature: 'pronunciation-coaching'`.
|
|
14
|
+
- **ai-check:** a `pronunciationCoaching` port runs four reading cases: a mispronounced "th" and a word left out, a clean reading, the same slips from a stored grade without sounds, and coaching asked for in Spanish.
|
|
15
|
+
- Six new strings: `aiCoaching`, `aiCoachingLoading`, `aiCoachingHeading`, `aiCoachingUnavailable`, `aiCoachingWords`, `aiCoachingSound`.
|
|
16
|
+
- **Stability:** the promise now says what a minor may add to a type the SDK hands you — a union member, a `LkStrings` key, an optional property — and that a `default` branch keeps a switch correct across minors.
|
|
17
|
+
|
|
18
|
+
## 23.0.1
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- [#84](https://github.com/intellectif/learning-kit/pull/84) [`c037af8`](https://github.com/intellectif/learning-kit/commit/c037af83d73ec095e24853794cb299149bd7964f) Thanks [@diego28e](https://github.com/diego28e)! - **AI help spans the answer**: the "Explain my answer" panel and the list of hints now take the full width of the question, as feedback on a draft already did.
|
|
23
|
+
|
|
24
|
+
**Action required:** none. Only the skin changes: no markup, prop or string.
|
|
25
|
+
|
|
26
|
+
- The live region inside `.lk-ai` lined up at the start like the button beside it, so it shrank to its content: a short explanation sat in a panel only as wide as its text, and the hints widened each time a longer one arrived. `.lk-ai > [aria-live]` now stretches, which covers the explanation, the hints and feedback on a draft, so feedback on a draft no longer needs a rule of its own.
|
|
27
|
+
- Where the text already filled the line, as on a phone, nothing moves.
|
|
28
|
+
|
|
3
29
|
## 23.0.0
|
|
4
30
|
|
|
5
31
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ export function Demo() {
|
|
|
75
75
|
- **`<InteractiveVideo>`** — a video that stops at each quiz and asks its questions over itself, then plays on. **A formative tool**, for lessons and practice — deliver official summative exams with `<ActivitySequence>`. One player: a progress bar that marks every quiz and previews the caption under the pointer, speed, volume, captions drawn from WebVTT it parses itself (through a `captionsLoader` when the file needs the learner's credentials) — in one language or two at once, a second line under the first for a learner matching what they hear to what it means — a contents and transcript panel, picture-in-picture, fullscreen and a keyboard map with a shortcut sheet. A quiz covers the video and grows the player rather than scrolling inside it, every question of an opened quiz stays mounted so a rewind loses no answer, the controls are `inert` while it is open, and a **required** quiz holds both playback and seeking until it is answered. Answers report with the slot the question sits in and the quiz it belongs to, and `onProgress` gives a resume point that is never past an unanswered required quiz. A host with renderers of its own draws any question itself through `renderQuestion` — the video still counts it, tells it when it leaves the screen so it can stop its microphone, and holds Finish while its answer is still on its way. See the [interactive video guide](https://github.com/intellectif/learning-kit/blob/main/docs/interactive-video.md).
|
|
76
76
|
- **Delivery policies** — `delivery` on any activity, `<ActivitySequence>` or `<InteractiveVideo>` switches off feedback, solutions, hints or AI help for that paper, so one question serves practice and a final without being authored twice. It can only take away, an empty policy is exactly the default, and a pager's policy holds in every question it holds — a host's own renderer included. See [Delivery policies](https://github.com/intellectif/learning-kit/blob/main/docs/delivery.md).
|
|
77
77
|
- **Scoring policies** — `scoring` on any activity, `<ActivitySequence>` or `<InteractiveVideo>` gives a practice question "Try again", chooses which try counts, and charges for tries and hints, saying what a hint costs before the learner asks. The right answer and "Explain my answer" wait until the tries end, a set waits for a question being tried again, and no policy changes nothing. See [Scoring policies](https://github.com/intellectif/learning-kit/blob/main/docs/scoring.md).
|
|
78
|
-
- **AI help for learners** — "Explain my answer" after grading and "Get a hint" before submit, in `<MultipleChoice>`, `<FillInTheBlanks>`, `<GapSelect>` and `<Dictation>` (explanations only),
|
|
78
|
+
- **AI help for learners** — "Explain my answer" after grading and "Get a hint" before submit, in `<MultipleChoice>`, `<FillInTheBlanks>`, `<GapSelect>` and `<Dictation>` (explanations only), "Get feedback on my draft" in `<WrittenResponse>` in practice — revise and ask again, and a correction that quotes words the learner never wrote refuses the feedback — and "Coach me on this reading" under the marks of `<ReadAloud>` and `<PronunciationFeedback>`, where coaching on a word the speech engine did not mark is refused, through ports **you** supply with `<LkAiProvider>` or an `ai` prop (also on `<ActivitySequence>` and `<InteractiveVideo>`). The SDK never calls a model or holds a key: it builds the facts your model is given, refuses an explanation that contradicts the grade and a hint that contains the answer, marks everything a model wrote, and drops a late answer. Never in `exam`. What was shown reaches `onInteraction` with the model that wrote it and what the call cost; what was refused reaches it with the reason, and never the text. See the [AI guide](https://github.com/intellectif/learning-kit/blob/main/docs/ai.md).
|
|
79
79
|
- **`<ActivitySequence>`** — in-place "question set" pager (Previous/Next, "Question X of N", no scrolling, focus-managed). Accepts item groups and keeps their stimulus beside every question; `onSubmit` reports every raw answer with its `slotId`, which is the only response channel an `exam` sequence has. `onFinished` is the completion signal for a set that mixes scored and deferred-graded items, where `onComplete` can never fire. A read-aloud slot takes its storage from `recordingBinding`, whose methods are told which slot a take belongs to, and a `review` its per-word marks from `assessments`, keyed by `slotId` — there is a [worked example](https://github.com/intellectif/learning-kit/blob/main/docs/speech-assessment.md#in-a-question-set).
|
|
80
80
|
- **Resume and review a whole attempt** — `<ActivitySequence>` takes `defaultIndex`, `responses` and `submittedSlotIds` (read at mount; remount with a `key` to show a different attempt), so an interrupted paper reopens on the right question with the right answers and already-committed questions still committed. `onIndexChange` reports every position the pager lands on, including a clamp it had to apply. Pass `renderMode="review"` plus server-computed `outcomes` to render a finished attempt read-only. Pairs with `serializeAttemptState` / `restoreAttemptState` in `lk-core`.
|
|
81
81
|
- **`renderMode`** — `practice` (default: the component scores locally and reveals correctness), `exam` (never scores, never reveals; submit emits the raw response for the server to grade), `review` (read-only, marks correctness only from an `outcome` you supply). This is the single switch that takes grading off the client.
|
|
@@ -85,7 +85,7 @@ export function Demo() {
|
|
|
85
85
|
- **`<StimulusPanel>`** — a shared passage / recording / image (an item group's stimulus) as a landmark region; the sequence uses it, and a custom runner can too.
|
|
86
86
|
- **`<ActivityPreview>`** — an editor's preview. Renders a draft in any `renderMode` with a simulated `response` (marked with `evaluate()` in `review`), and a translatable notice — or your `fallback`, handed the issues — while `validateDraft` says the draft is not finished. Nothing it renders is recorded, and a recording's play limit is counted in memory only.
|
|
87
87
|
- **Custom activity types** — `renderers={{ 'my-type': MyRenderer }}` on the sequence; a key matching a built-in overrides it, so you can replace a bundled renderer without forking the sequencer. A renderer is given the same contract the SDK's own components have: a `question` prop that says when it is the question on screen (so it can stop its microphone), holds the set while its own work is in flight, gives it somewhere to portal a popover, and reports what the learner did through calls that keep one identity.
|
|
88
|
-
- **AI help in a question you draw** — `useAiHints`, `useAiExplanation` and `
|
|
88
|
+
- **AI help in a question you draw** — `useAiHints`, `useAiExplanation`, `useAiWritingFeedback` and `useAiCoaching` give a host's own renderer the rules, the facts and the refusals the bundled components use, without their buttons. `ask` does nothing where the rules say no, so an exam reaches no model however the page asks.
|
|
89
89
|
- **Rich text, opt-in** — `sanitizeHtml` renders author-supplied `questionHtml` / `promptHtml`. The SDK ships **no** sanitiser and injects no HTML without one; without it the escaped plain-text field is used. (`FillInTheBlanks` ignores `passageHtml` by design — its passage hosts the answer inputs.)
|
|
90
90
|
- **Media per question** — optional `image` / `audio` / `video` / `embed` (YouTube/Vimeo iframe) above the question; alt-text required for `image`/`embed` (WCAG).
|
|
91
91
|
- **Playback policy for listening papers** — an audio recording can declare `maxPlays`, `seek: 'none'` and `rate: 'fixed'`. The SDK then renders its own accessible transport (44px targets, a live plays-remaining status, an exhausted button that stays focusable and says why) and enforces the policy from the element's own events, so a hardware media key goes through the budget too. Wire `mediaBudget` on `<ActivitySequence>` to make a play budget survive a refresh.
|
|
@@ -95,7 +95,7 @@ export function Demo() {
|
|
|
95
95
|
- **`useSpeechRecorder(options)`** — the capture half of a read-aloud, usable on its own: microphone permission, an RMS level for a meter, a minimum take length, and an auto-stop measured from the **sample count** rather than a timer, so a take can never outrun its own limit. It yields **16 kHz mono 16-bit PCM WAV** — the one format `inspectWav` in `lk-core` measures without a decoder. SSR-safe by construction (support is discovered inside `start()`, never probed during render, so a server render and its hydration cannot disagree), and the microphone and the audio context are released on stop, discard, error and unmount alike. Where a Content-Security-Policy's `script-src` does not allow `blob:`, serve `CAPTURE_PROCESSOR_SOURCE` — exported beside the hook, with the contract it meets written on it — and pass its URL as `workletUrl`. A `workletUrl` that fails to load is not reported: the hook falls back to the main-thread `ScriptProcessorNode`, so check that the module is fetched.
|
|
96
96
|
- **`<ThemeProvider>` + `defaults.css`** — `--lk-*` design-token system; automatic dark mode via `prefers-color-scheme` (SSR-safe with `useSyncExternalStore`).
|
|
97
97
|
- **`createTailwindTheme(theme)`** — optional Tailwind interop; consume the SDK palette from your own utilities.
|
|
98
|
-
- **`<LkIntlProvider>`** — all
|
|
98
|
+
- **`<LkIntlProvider>`** — all 202 strings the SDK's own chrome renders, replaceable in one place, with a per-component `strings` prop for the exceptions. Interpolation and plurals are **functions**, not format strings, so your `Intl.PluralRules` does the work and TypeScript checks the arity. `locale` sets `lang` and derives `dir`; the skin uses logical properties, so RTL follows. The SDK ships the mechanism and **English only** — see [docs/i18n.md](https://github.com/intellectif/learning-kit/blob/main/docs/i18n.md).
|
|
99
99
|
- **WCAG 2.2 AA** — axe-clean unit + Playwright e2e tests; full keyboard operability; numerically-verified contrast.
|
|
100
100
|
- **RSC-compatible** — every component carries `'use client'` and hydrates inside a React Server Component tree.
|
|
101
101
|
|
|
@@ -119,7 +119,7 @@ export function Demo() {
|
|
|
119
119
|
| `@intellectif/lk-react/hooks/useSpeechRecorder` | Microphone capture to 16 kHz mono WAV, and `CAPTURE_PROCESSOR_SOURCE` for a self-hosted worklet |
|
|
120
120
|
| `@intellectif/lk-react/hooks/useXAPI` | LRS delivery (retry, never-throws) |
|
|
121
121
|
| `@intellectif/lk-react/ai/LkAiProvider` | `<LkAiProvider>`, `useLearnerAi`, and the `LearnerAi` type |
|
|
122
|
-
| `@intellectif/lk-react/ai/useAiHelp` | `useAiHints`, `useAiExplanation`, `useAiWritingFeedback` — the AI rules without the SDK's own buttons |
|
|
122
|
+
| `@intellectif/lk-react/ai/useAiHelp` | `useAiHints`, `useAiExplanation`, `useAiWritingFeedback`, `useAiCoaching` — the AI rules without the SDK's own buttons |
|
|
123
123
|
| `@intellectif/lk-react/i18n/LkIntlProvider` | `<LkIntlProvider>`, `useLkStrings`, `useLkDirection`, `DEFAULT_STRINGS`, `mergeStrings`, `directionForLocale` |
|
|
124
124
|
| `@intellectif/lk-react/theme/ThemeProvider` | `<ThemeProvider>`, `darkTheme`, `useTheme`, `createTailwindTheme` |
|
|
125
125
|
| `@intellectif/lk-react/theme/defaults.css` | Tokens (required) |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { XAPIStatement, WrittenResponseData, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens, DeliveryPolicy } from '@intellectif/lk-core';
|
|
2
2
|
import { LearnerAi } from './ai/LkAiProvider.js';
|
|
3
|
-
import { f as Renderable, e as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, b as MediaTransportStrings, a as LkStringsOverride } from './types-
|
|
3
|
+
import { f as Renderable, e as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, b as MediaTransportStrings, a as LkStringsOverride } from './types-C4IsUQsK.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Payload delivered when the learner submits a written response. Grading is
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { XAPIStatement, WrittenResponseData, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens, DeliveryPolicy } from '@intellectif/lk-core';
|
|
2
2
|
import { LearnerAi } from './ai/LkAiProvider.cjs';
|
|
3
|
-
import { f as Renderable, e as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, b as MediaTransportStrings, a as LkStringsOverride } from './types-
|
|
3
|
+
import { f as Renderable, e as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, b as MediaTransportStrings, a as LkStringsOverride } from './types-CJgZWtl4.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Payload delivered when the learner submits a written response. Grading is
|
package/dist/ai/LkAiProvider.cjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
4
|
+
var chunkTUVENMXB_cjs = require('../chunk-TUVENMXB.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
Object.defineProperty(exports, "LkAiProvider", {
|
|
9
9
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
10
|
+
get: function () { return chunkTUVENMXB_cjs.LkAiProvider; }
|
|
11
11
|
});
|
|
12
12
|
Object.defineProperty(exports, "useLearnerAi", {
|
|
13
13
|
enumerable: true,
|
|
14
|
-
get: function () { return
|
|
14
|
+
get: function () { return chunkTUVENMXB_cjs.useLearnerAi; }
|
|
15
15
|
});
|
|
16
16
|
//# sourceMappingURL=LkAiProvider.cjs.map
|
|
17
17
|
//# sourceMappingURL=LkAiProvider.cjs.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AiExplanationRequest, AiTextResult, AiHintRequest, AiWritingFeedbackRequest, AiWritingFeedbackResult } from '@intellectif/lk-core';
|
|
1
|
+
import { AiExplanationRequest, AiTextResult, AiHintRequest, AiWritingFeedbackRequest, AiWritingFeedbackResult, AiCoachingRequest, AiCoachingResult } from '@intellectif/lk-core';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -40,7 +40,17 @@ interface LearnerAi {
|
|
|
40
40
|
/** How many times a learner can ask for feedback on one written response. Default 3, at most 10. */
|
|
41
41
|
maxWritingFeedback?: number;
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* Coaches a learner on a graded reading aloud: what the speech engine's
|
|
44
|
+
* marks mean and how to practise, word by word. The marks are the engine's
|
|
45
|
+
* and are never re-scored; coaching on a word the engine did not mark, or a
|
|
46
|
+
* sound it did not report, is refused whole. See `checkAiCoaching` in
|
|
47
|
+
* lk-core. One coaching per reading.
|
|
48
|
+
*/
|
|
49
|
+
pronunciationCoaching?: (request: AiCoachingRequest, options: {
|
|
50
|
+
signal: AbortSignal;
|
|
51
|
+
}) => Promise<AiCoachingResult>;
|
|
52
|
+
/**
|
|
53
|
+
* The language to write explanations, hints, feedback and coaching in, as a tag (`es`,
|
|
44
54
|
* `pt-BR`). Defaults to the component's `locale`: the interface language,
|
|
45
55
|
* which the buttons around the help are in, and usually the right one. In a
|
|
46
56
|
* language course it need not be the item's — English items, explained in
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AiExplanationRequest, AiTextResult, AiHintRequest, AiWritingFeedbackRequest, AiWritingFeedbackResult } from '@intellectif/lk-core';
|
|
1
|
+
import { AiExplanationRequest, AiTextResult, AiHintRequest, AiWritingFeedbackRequest, AiWritingFeedbackResult, AiCoachingRequest, AiCoachingResult } from '@intellectif/lk-core';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -40,7 +40,17 @@ interface LearnerAi {
|
|
|
40
40
|
/** How many times a learner can ask for feedback on one written response. Default 3, at most 10. */
|
|
41
41
|
maxWritingFeedback?: number;
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* Coaches a learner on a graded reading aloud: what the speech engine's
|
|
44
|
+
* marks mean and how to practise, word by word. The marks are the engine's
|
|
45
|
+
* and are never re-scored; coaching on a word the engine did not mark, or a
|
|
46
|
+
* sound it did not report, is refused whole. See `checkAiCoaching` in
|
|
47
|
+
* lk-core. One coaching per reading.
|
|
48
|
+
*/
|
|
49
|
+
pronunciationCoaching?: (request: AiCoachingRequest, options: {
|
|
50
|
+
signal: AbortSignal;
|
|
51
|
+
}) => Promise<AiCoachingResult>;
|
|
52
|
+
/**
|
|
53
|
+
* The language to write explanations, hints, feedback and coaching in, as a tag (`es`,
|
|
44
54
|
* `pt-BR`). Defaults to the component's `locale`: the interface language,
|
|
45
55
|
* which the buttons around the help are in, and usually the right one. In a
|
|
46
56
|
* language course it need not be the item's — English items, explained in
|
package/dist/ai/LkAiProvider.js
CHANGED
package/dist/ai/useAiHelp.cjs
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
require('../chunk-
|
|
6
|
-
require('../chunk-J2QIHZ3H.cjs');
|
|
7
|
-
require('../chunk-54DX6SLS.cjs');
|
|
4
|
+
var chunkLPUWXIPR_cjs = require('../chunk-LPUWXIPR.cjs');
|
|
5
|
+
require('../chunk-TUVENMXB.cjs');
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
|
|
9
|
+
Object.defineProperty(exports, "useAiCoaching", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: function () { return chunkLPUWXIPR_cjs.useAiCoaching; }
|
|
12
|
+
});
|
|
11
13
|
Object.defineProperty(exports, "useAiExplanation", {
|
|
12
14
|
enumerable: true,
|
|
13
|
-
get: function () { return
|
|
15
|
+
get: function () { return chunkLPUWXIPR_cjs.useAiExplanation; }
|
|
14
16
|
});
|
|
15
17
|
Object.defineProperty(exports, "useAiHints", {
|
|
16
18
|
enumerable: true,
|
|
17
|
-
get: function () { return
|
|
19
|
+
get: function () { return chunkLPUWXIPR_cjs.useAiHints; }
|
|
18
20
|
});
|
|
19
21
|
Object.defineProperty(exports, "useAiWritingFeedback", {
|
|
20
22
|
enumerable: true,
|
|
21
|
-
get: function () { return
|
|
23
|
+
get: function () { return chunkLPUWXIPR_cjs.useAiWritingFeedback; }
|
|
22
24
|
});
|
|
23
25
|
//# sourceMappingURL=useAiHelp.cjs.map
|
|
24
26
|
//# sourceMappingURL=useAiHelp.cjs.map
|
package/dist/ai/useAiHelp.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { AiCoaching, ReadAloudData, SpeechAssessment, GradeRecord, InteractionEvent, DeliveryPolicy, AiTextResult, LearnerResponse, ItemOutcome, AiWritingFeedback } from '@intellectif/lk-core';
|
|
2
|
+
import { e as RenderMode, g as RenderableActivity } from '../types-CJgZWtl4.cjs';
|
|
3
3
|
import { LearnerAi } from './LkAiProvider.cjs';
|
|
4
4
|
import '../machine-CuwKqWXC.cjs';
|
|
5
5
|
import 'react';
|
|
@@ -203,5 +203,84 @@ interface AiWritingFeedbackHelp {
|
|
|
203
203
|
* grade.
|
|
204
204
|
*/
|
|
205
205
|
declare function useAiWritingFeedback(input: AiWritingFeedbackInput): AiWritingFeedbackHelp;
|
|
206
|
+
interface AiCoachingInput {
|
|
207
|
+
/**
|
|
208
|
+
* The read-aloud item, as `data` is given to `<ReadAloud>` — or as much of
|
|
209
|
+
* one as coaching reads: its id, title, text and language, and the author's
|
|
210
|
+
* `instructions` and `ai` settings where it has them.
|
|
211
|
+
*/
|
|
212
|
+
data: Pick<ReadAloudData, 'id' | 'title' | 'referenceText' | 'locale'> & Partial<Pick<ReadAloudData, 'instructions' | 'ai'>>;
|
|
213
|
+
/**
|
|
214
|
+
* The speech engine's assessment of the take, as the learner is shown it.
|
|
215
|
+
* When given, it is the marks — each word's, with its sounds and what each
|
|
216
|
+
* was heard as — and the only ones: see `aiCoachingRequest` in lk-core.
|
|
217
|
+
*/
|
|
218
|
+
assessment?: SpeechAssessment | null;
|
|
219
|
+
/**
|
|
220
|
+
* The grade of record. Its score is context for the model; without an
|
|
221
|
+
* assessment, its word details are the marks, and they carry no sounds.
|
|
222
|
+
*/
|
|
223
|
+
grade?: GradeRecord | null;
|
|
224
|
+
/**
|
|
225
|
+
* Pass on the `renderMode` your reading was given. Coaching is never offered
|
|
226
|
+
* in `exam`, and an `exam` around the reading wins over anything passed here.
|
|
227
|
+
*/
|
|
228
|
+
renderMode?: RenderMode;
|
|
229
|
+
/** The AI ports to use, overriding {@link LkAiProvider}'s for this reading. */
|
|
230
|
+
ai?: LearnerAi;
|
|
231
|
+
/** The interface language: the default language for what a model writes. */
|
|
232
|
+
locale?: string;
|
|
233
|
+
/** Where `ai-coaching-shown` and `ai-help-refused` go. */
|
|
234
|
+
onInteraction?: (event: InteractionEvent) => void;
|
|
235
|
+
/**
|
|
236
|
+
* The delivery policy your reading was handed. Coaching needs feedback shown
|
|
237
|
+
* and AI explanations allowed; the paper around the reading holds as well.
|
|
238
|
+
*/
|
|
239
|
+
delivery?: DeliveryPolicy | null;
|
|
240
|
+
}
|
|
241
|
+
interface AiCoachingHelp {
|
|
242
|
+
/**
|
|
243
|
+
* Whether to offer coaching at all: a `pronunciationCoaching` port is
|
|
244
|
+
* present, there are marks to coach, the mode is not `exam`, the item's
|
|
245
|
+
* author left explanations on, and the paper shows feedback and allows AI
|
|
246
|
+
* explanations.
|
|
247
|
+
*/
|
|
248
|
+
offered: boolean;
|
|
249
|
+
status: 'idle' | 'loading' | 'shown' | 'unavailable';
|
|
250
|
+
/**
|
|
251
|
+
* What to show, once it has arrived and been checked: the coaching's text,
|
|
252
|
+
* and the words it works on in reading order, each with its tip and, where
|
|
253
|
+
* the engine reported one, the sound. All of it is text, never HTML.
|
|
254
|
+
*/
|
|
255
|
+
coaching: AiCoaching | null;
|
|
256
|
+
/**
|
|
257
|
+
* Asks the port, when the learner presses something. Does nothing while a
|
|
258
|
+
* call is on its way, once coaching is shown — one per reading — or where
|
|
259
|
+
* `offered` is false. One identity for the life of the reading.
|
|
260
|
+
*/
|
|
261
|
+
ask: () => void;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* "Coach me on this reading", without the SDK's own button and panel: the
|
|
265
|
+
* rules, the call, the checks and the record, for marks you draw yourself.
|
|
266
|
+
*
|
|
267
|
+
* It is what `<PronunciationFeedback>` and `<ReadAloud>` use, so a host's own
|
|
268
|
+
* marks get coaching on the same terms: never in `exam`, never where the
|
|
269
|
+
* item's author switched explanations off, and never coaching on a word the
|
|
270
|
+
* engine did not mark or a sound it did not report — which is refused whole.
|
|
271
|
+
*
|
|
272
|
+
* ```tsx
|
|
273
|
+
* const { offered, status, coaching, ask } = useAiCoaching({
|
|
274
|
+
* data: item, assessment, grade, renderMode, ai,
|
|
275
|
+
* });
|
|
276
|
+
* ```
|
|
277
|
+
*
|
|
278
|
+
* **Say who wrote it**, as the SDK's own panel does: "Written by AI. It can make
|
|
279
|
+
* mistakes." The marks beside it are the engine's; the coaching is a model's.
|
|
280
|
+
*
|
|
281
|
+
* Another reading — other marks, another text — drops the coaching and
|
|
282
|
+
* abandons a call still on its way, as the caller unmounting does.
|
|
283
|
+
*/
|
|
284
|
+
declare function useAiCoaching(input: AiCoachingInput): AiCoachingHelp;
|
|
206
285
|
|
|
207
|
-
export { type AiExplanationHelp, type AiExplanationInput, type AiHelpSituation, type AiHintsHelp, type AiHintsInput, type AiWritingFeedbackHelp, type AiWritingFeedbackInput, useAiExplanation, useAiHints, useAiWritingFeedback };
|
|
286
|
+
export { type AiCoachingHelp, type AiCoachingInput, type AiExplanationHelp, type AiExplanationInput, type AiHelpSituation, type AiHintsHelp, type AiHintsInput, type AiWritingFeedbackHelp, type AiWritingFeedbackInput, useAiCoaching, useAiExplanation, useAiHints, useAiWritingFeedback };
|
package/dist/ai/useAiHelp.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { AiCoaching, ReadAloudData, SpeechAssessment, GradeRecord, InteractionEvent, DeliveryPolicy, AiTextResult, LearnerResponse, ItemOutcome, AiWritingFeedback } from '@intellectif/lk-core';
|
|
2
|
+
import { e as RenderMode, g as RenderableActivity } from '../types-C4IsUQsK.js';
|
|
3
3
|
import { LearnerAi } from './LkAiProvider.js';
|
|
4
4
|
import '../machine-CuwKqWXC.js';
|
|
5
5
|
import 'react';
|
|
@@ -203,5 +203,84 @@ interface AiWritingFeedbackHelp {
|
|
|
203
203
|
* grade.
|
|
204
204
|
*/
|
|
205
205
|
declare function useAiWritingFeedback(input: AiWritingFeedbackInput): AiWritingFeedbackHelp;
|
|
206
|
+
interface AiCoachingInput {
|
|
207
|
+
/**
|
|
208
|
+
* The read-aloud item, as `data` is given to `<ReadAloud>` — or as much of
|
|
209
|
+
* one as coaching reads: its id, title, text and language, and the author's
|
|
210
|
+
* `instructions` and `ai` settings where it has them.
|
|
211
|
+
*/
|
|
212
|
+
data: Pick<ReadAloudData, 'id' | 'title' | 'referenceText' | 'locale'> & Partial<Pick<ReadAloudData, 'instructions' | 'ai'>>;
|
|
213
|
+
/**
|
|
214
|
+
* The speech engine's assessment of the take, as the learner is shown it.
|
|
215
|
+
* When given, it is the marks — each word's, with its sounds and what each
|
|
216
|
+
* was heard as — and the only ones: see `aiCoachingRequest` in lk-core.
|
|
217
|
+
*/
|
|
218
|
+
assessment?: SpeechAssessment | null;
|
|
219
|
+
/**
|
|
220
|
+
* The grade of record. Its score is context for the model; without an
|
|
221
|
+
* assessment, its word details are the marks, and they carry no sounds.
|
|
222
|
+
*/
|
|
223
|
+
grade?: GradeRecord | null;
|
|
224
|
+
/**
|
|
225
|
+
* Pass on the `renderMode` your reading was given. Coaching is never offered
|
|
226
|
+
* in `exam`, and an `exam` around the reading wins over anything passed here.
|
|
227
|
+
*/
|
|
228
|
+
renderMode?: RenderMode;
|
|
229
|
+
/** The AI ports to use, overriding {@link LkAiProvider}'s for this reading. */
|
|
230
|
+
ai?: LearnerAi;
|
|
231
|
+
/** The interface language: the default language for what a model writes. */
|
|
232
|
+
locale?: string;
|
|
233
|
+
/** Where `ai-coaching-shown` and `ai-help-refused` go. */
|
|
234
|
+
onInteraction?: (event: InteractionEvent) => void;
|
|
235
|
+
/**
|
|
236
|
+
* The delivery policy your reading was handed. Coaching needs feedback shown
|
|
237
|
+
* and AI explanations allowed; the paper around the reading holds as well.
|
|
238
|
+
*/
|
|
239
|
+
delivery?: DeliveryPolicy | null;
|
|
240
|
+
}
|
|
241
|
+
interface AiCoachingHelp {
|
|
242
|
+
/**
|
|
243
|
+
* Whether to offer coaching at all: a `pronunciationCoaching` port is
|
|
244
|
+
* present, there are marks to coach, the mode is not `exam`, the item's
|
|
245
|
+
* author left explanations on, and the paper shows feedback and allows AI
|
|
246
|
+
* explanations.
|
|
247
|
+
*/
|
|
248
|
+
offered: boolean;
|
|
249
|
+
status: 'idle' | 'loading' | 'shown' | 'unavailable';
|
|
250
|
+
/**
|
|
251
|
+
* What to show, once it has arrived and been checked: the coaching's text,
|
|
252
|
+
* and the words it works on in reading order, each with its tip and, where
|
|
253
|
+
* the engine reported one, the sound. All of it is text, never HTML.
|
|
254
|
+
*/
|
|
255
|
+
coaching: AiCoaching | null;
|
|
256
|
+
/**
|
|
257
|
+
* Asks the port, when the learner presses something. Does nothing while a
|
|
258
|
+
* call is on its way, once coaching is shown — one per reading — or where
|
|
259
|
+
* `offered` is false. One identity for the life of the reading.
|
|
260
|
+
*/
|
|
261
|
+
ask: () => void;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* "Coach me on this reading", without the SDK's own button and panel: the
|
|
265
|
+
* rules, the call, the checks and the record, for marks you draw yourself.
|
|
266
|
+
*
|
|
267
|
+
* It is what `<PronunciationFeedback>` and `<ReadAloud>` use, so a host's own
|
|
268
|
+
* marks get coaching on the same terms: never in `exam`, never where the
|
|
269
|
+
* item's author switched explanations off, and never coaching on a word the
|
|
270
|
+
* engine did not mark or a sound it did not report — which is refused whole.
|
|
271
|
+
*
|
|
272
|
+
* ```tsx
|
|
273
|
+
* const { offered, status, coaching, ask } = useAiCoaching({
|
|
274
|
+
* data: item, assessment, grade, renderMode, ai,
|
|
275
|
+
* });
|
|
276
|
+
* ```
|
|
277
|
+
*
|
|
278
|
+
* **Say who wrote it**, as the SDK's own panel does: "Written by AI. It can make
|
|
279
|
+
* mistakes." The marks beside it are the engine's; the coaching is a model's.
|
|
280
|
+
*
|
|
281
|
+
* Another reading — other marks, another text — drops the coaching and
|
|
282
|
+
* abandons a call still on its way, as the caller unmounting does.
|
|
283
|
+
*/
|
|
284
|
+
declare function useAiCoaching(input: AiCoachingInput): AiCoachingHelp;
|
|
206
285
|
|
|
207
|
-
export { type AiExplanationHelp, type AiExplanationInput, type AiHelpSituation, type AiHintsHelp, type AiHintsInput, type AiWritingFeedbackHelp, type AiWritingFeedbackInput, useAiExplanation, useAiHints, useAiWritingFeedback };
|
|
286
|
+
export { type AiCoachingHelp, type AiCoachingInput, type AiExplanationHelp, type AiExplanationInput, type AiHelpSituation, type AiHintsHelp, type AiHintsInput, type AiWritingFeedbackHelp, type AiWritingFeedbackInput, useAiCoaching, useAiExplanation, useAiHints, useAiWritingFeedback };
|
package/dist/ai/useAiHelp.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
export { useAiExplanation, useAiHints, useAiWritingFeedback } from '../chunk-
|
|
3
|
-
import '../chunk-
|
|
4
|
-
import '../chunk-U4KTOH7C.js';
|
|
5
|
-
import '../chunk-DXFCYNST.js';
|
|
2
|
+
export { useAiCoaching, useAiExplanation, useAiHints, useAiWritingFeedback } from '../chunk-MXNC5UNO.js';
|
|
3
|
+
import '../chunk-ZPI6Q42R.js';
|
|
6
4
|
//# sourceMappingURL=useAiHelp.js.map
|
|
7
5
|
//# sourceMappingURL=useAiHelp.js.map
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
var
|
|
5
|
-
var chunk54DX6SLS_cjs = require('./chunk-54DX6SLS.cjs');
|
|
4
|
+
var chunkLPUWXIPR_cjs = require('./chunk-LPUWXIPR.cjs');
|
|
6
5
|
var lkCore = require('@intellectif/lk-core');
|
|
7
6
|
var react = require('react');
|
|
8
7
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
8
|
|
|
10
9
|
function useDeliveryPolicy(given) {
|
|
11
|
-
const around = react.useContext(
|
|
10
|
+
const around = react.useContext(chunkLPUWXIPR_cjs.SequenceSlotContext)?.delivery;
|
|
12
11
|
const key = policyKey(given);
|
|
13
12
|
react.useEffect(() => {
|
|
14
|
-
if (!
|
|
13
|
+
if (!chunkLPUWXIPR_cjs.isDevelopment() || given === void 0 || given === null) {
|
|
15
14
|
return;
|
|
16
15
|
}
|
|
17
16
|
const checked = lkCore.validateDeliveryPolicy(given);
|
|
@@ -51,5 +50,5 @@ function FeedbackRegion({ id, className, children, ref }) {
|
|
|
51
50
|
exports.FeedbackRegion = FeedbackRegion;
|
|
52
51
|
exports.outcomeShowsMarks = outcomeShowsMarks;
|
|
53
52
|
exports.useDeliveryPolicy = useDeliveryPolicy;
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
55
|
-
//# sourceMappingURL=chunk-
|
|
53
|
+
//# sourceMappingURL=chunk-2PNSVBLQ.cjs.map
|
|
54
|
+
//# sourceMappingURL=chunk-2PNSVBLQ.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/shared/delivery.ts","../src/components/shared/FeedbackRegion.tsx"],"names":["useContext","SequenceSlotContext","useEffect","isDevelopment","validateDeliveryPolicy","combineDeliveryPolicies","jsx"],"mappings":";;;;;;;AAyBO,SAAS,kBACd,KAAA,EACwB;AACxB,EAAA,MAAM,MAAA,GAASA,gBAAA,CAAWC,qCAAmB,CAAA,EAAG,QAAA;AAChD,EAAA,MAAM,GAAA,GAAM,UAAU,KAAK,CAAA;AAE3B,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAACC,+BAAA,EAAc,IAAK,KAAA,KAAU,MAAA,IAAa,UAAU,IAAA,EAAM;AAC7D,MAAA;AAAA,IACF;AACA,IAAA,MAAM,OAAA,GAAUC,8BAAuB,KAAK,CAAA;AAC5C,IAAA,IAAI,CAAC,QAAQ,OAAA,EAAS;AACpB,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,6GAA6G,OAAA,CAAQ,MAAA,CAClH,GAAA,CAAI,CAAC,UAAU,CAAA,EAAG,KAAA,CAAM,IAAA,IAAQ,QAAQ,WAAM,KAAA,CAAM,OAAO,EAAE,CAAA,CAC7D,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,OACf;AAAA,IACF;AAAA,EACF,CAAA,EAAG,CAAC,GAAG,CAAC,CAAA;AACR,EAAA,OAAOC,8BAAA,CAAwB,OAAO,MAAM,CAAA;AAC9C;AAGA,SAAS,UAAU,KAAA,EAAwB;AACzC,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,IAAK,EAAA;AAAA,EAClC,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,OAAO,KAAK,CAAA;AAAA,EACrB;AACF;AAOO,SAAS,kBAAkB,OAAA,EAA2C;AAC3E,EAAA,OAAO,OAAA,EAAS,MAAA,KAAW,QAAA,IAAY,OAAA,EAAS,MAAA,KAAW,QAAA;AAC7D;AC3CO,SAAS,eAAe,EAAE,EAAA,EAAI,SAAA,EAAW,QAAA,EAAU,KAAI,EAAwB;AACpF,EAAA,uBACEC,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,EAAA;AAAA,MACA,SAAA;AAAA,MACA,WAAA,EAAU,QAAA;AAAA,MACV,aAAA,EAAY,MAAA;AAAA,MACZ,GAAA;AAAA,MACC,GAAI,GAAA,KAAQ,MAAA,GAAY,EAAE,QAAA,EAAU,EAAA,KAAO,EAAC;AAAA,MAE5C;AAAA;AAAA,GACH;AAEJ","file":"chunk-2PNSVBLQ.cjs","sourcesContent":["'use client';\n\nimport {\n combineDeliveryPolicies,\n type DeliveryPolicy,\n type ItemOutcome,\n type ResolvedDeliveryPolicy,\n validateDeliveryPolicy,\n} from '@intellectif/lk-core';\nimport { useContext, useEffect } from 'react';\nimport { isDevelopment } from '../_internal.js';\nimport { SequenceSlotContext } from './sequence-slot.js';\n\n/**\n * The delivery policy a question is under: the one it was handed, and the one\n * of the paper around it, whichever is stricter setting by setting.\n *\n * The paper's comes from the slot channel, never from a prop, for the reason\n * the mode does: a host that draws its own question is expected to pass\n * `delivery` on and nothing makes it. A paper that switched hints off keeps\n * them off in every question it holds, however that question was drawn.\n *\n * A policy that would not pass `validateDeliveryPolicy` is still applied — a\n * setting nobody can read restricts — and a development build says so once.\n */\nexport function useDeliveryPolicy(\n given: DeliveryPolicy | null | undefined,\n): ResolvedDeliveryPolicy {\n const around = useContext(SequenceSlotContext)?.delivery;\n const key = policyKey(given);\n // biome-ignore lint/correctness/useExhaustiveDependencies: the policy's content is the trigger, not its identity\n useEffect(() => {\n if (!isDevelopment() || given === undefined || given === null) {\n return;\n }\n const checked = validateDeliveryPolicy(given);\n if (!checked.success) {\n console.warn(\n `learning-kit: this delivery policy is not valid, and its unreadable settings are applied as restrictions: ${checked.issues\n .map((issue) => `${issue.path || 'policy'} — ${issue.message}`)\n .join('; ')}`,\n );\n }\n }, [key]);\n return combineDeliveryPolicies(given, around);\n}\n\n/** A policy's content as a string, so a new object with the same settings is the same policy. */\nfunction policyKey(given: unknown): string {\n try {\n return JSON.stringify(given) ?? '';\n } catch {\n return String(given);\n }\n}\n\n/**\n * Whether reading an outcome back says how the answer was marked — a score, a\n * pass or a fail — which a policy without `feedback` does not show. \"Not graded\n * yet\" and \"No grade available\" say nothing about the answer, and stay.\n */\nexport function outcomeShowsMarks(outcome: ItemOutcome | undefined): boolean {\n return outcome?.status === 'scored' || outcome?.status === 'graded';\n}\n","import type { ReactNode, Ref } from 'react';\n\nexport interface FeedbackRegionProps {\n /** Identifier so activity inputs can reference this region via aria. */\n id?: string;\n className?: string;\n children?: ReactNode;\n /**\n * Where focus can be put, when what focus was on goes away: \"Show answer\",\n * pressed, leaves the page. Given, the region takes `tabIndex={-1}` — focus\n * by script, never a stop in the tab order.\n */\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * A polite ARIA live region. Screen readers announce content injected here\n * (score, feedback, state changes) without moving focus. Pure/presentational\n * — safe to render in any environment.\n */\nexport function FeedbackRegion({ id, className, children, ref }: FeedbackRegionProps) {\n return (\n <div\n id={id}\n className={className}\n aria-live=\"polite\"\n aria-atomic=\"true\"\n ref={ref}\n {...(ref !== undefined ? { tabIndex: -1 } : {})}\n >\n {children}\n </div>\n );\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import { ActivityMedia } from './chunk-
|
|
3
|
-
import { useLkStrings } from './chunk-
|
|
2
|
+
import { ActivityMedia } from './chunk-TS7J6DTY.js';
|
|
3
|
+
import { useLkStrings } from './chunk-HNLRVRQR.js';
|
|
4
4
|
import { useId, useMemo } from 'react';
|
|
5
5
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
6
|
|
|
@@ -57,5 +57,5 @@ function StimulusPanel({
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export { StimulusPanel };
|
|
60
|
-
//# sourceMappingURL=chunk-
|
|
61
|
-
//# sourceMappingURL=chunk-
|
|
60
|
+
//# sourceMappingURL=chunk-4ZZWZSWI.js.map
|
|
61
|
+
//# sourceMappingURL=chunk-4ZZWZSWI.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/StimulusPanel/StimulusPanel.tsx"],"names":[],"mappings":"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","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/components/StimulusPanel/StimulusPanel.tsx"],"names":[],"mappings":";;;;;AAsDO,SAAS,aAAA,CAAc;AAAA,EAC5B,QAAA;AAAA,EACA,KAAA;AAAA,EACA,YAAA;AAAA,EACA,MAAA;AAAA,EACA,UAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,aAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAA0C;AACxC,EAAA,MAAM,UAAU,KAAA,EAAM;AACtB,EAAA,MAAM,CAAA,GAAI,aAAa,OAAO,CAAA;AAE9B,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAM;AAC7B,IAAA,IAAI,YAAA,KAAiB,MAAA,IAAa,OAAO,QAAA,CAAS,aAAa,QAAA,EAAU;AACvE,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,OAAO,YAAA,CAAa,SAAS,QAAQ,CAAA;AAAA,EACvC,CAAA,EAAG,CAAC,QAAA,CAAS,QAAA,EAAU,YAAY,CAAC,CAAA;AAEpC,EAAA,MAAM,SAAA,GAAY,UAAU,MAAA,GAAY,IAAA,GAAO,EAAE,aAAA,CAAc,KAAA,CAAM,KAAA,EAAO,KAAA,CAAM,IAAI,CAAA;AAEtF,EAAA,MAAM,MAAA,GACJ,QAAA,CAAS,KAAA,KAAU,MAAA,GACf,EAAE,iBAAA,EAAmB,OAAA,EAAQ,GAC7B,EAAE,YAAA,EAAc,CAAA,CAAE,YAAA,CAAa,QAAA,CAAS,IAAI,CAAA,EAAE;AASpD,EAAA,MAAM,WAAA,GAAc,SAAS,MAAA,KAAW,MAAA,GAAY,EAAE,IAAA,EAAM,QAAA,CAAS,MAAA,EAAO,GAAI,EAAC;AAEjF,EAAA,uBACE,IAAA,CAAC,SAAA,EAAA,EAAQ,SAAA,EAAU,aAAA,EAAc,WAAA,EAAW,SAAS,IAAA,EAAM,IAAA,EAAM,MAAA,EAAS,GAAG,MAAA,EAC1E,QAAA,EAAA;AAAA,IAAA,QAAA,CAAS,KAAA,KAAU,MAAA,mBAClB,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,mBAAA,EAAoB,EAAA,EAAI,OAAA,EAAU,GAAG,WAAA,EAC/C,QAAA,EAAA,QAAA,CAAS,KAAA,EACZ,CAAA,GACE,IAAA;AAAA,IACH,cAAc,IAAA,mBAAO,GAAA,CAAC,OAAE,SAAA,EAAU,mBAAA,EAAqB,qBAAU,CAAA,GAAO,IAAA;AAAA,IACxE,QAAA,CAAS,UAAU,MAAA,mBAClB,GAAA;AAAA,MAAC,aAAA;AAAA,MAAA;AAAA,QACC,OAAO,QAAA,CAAS,KAAA;AAAA,QACf,GAAI,UAAA,KAAe,MAAA,GAAY,EAAE,UAAA,KAAe,EAAC;AAAA,QACjD,GAAI,WAAA,KAAgB,MAAA,GAAY,EAAE,WAAA,KAAgB,EAAC;AAAA,QACnD,GAAI,YAAA,KAAiB,MAAA,GAAY,EAAE,YAAA,KAAiB,EAAC;AAAA,QACrD,GAAI,OAAA,KAAY,MAAA,GAAY,EAAE,OAAA,KAAY,EAAC;AAAA,QAC3C,GAAI,aAAA,KAAkB,MAAA,GAAY,EAAE,aAAA,KAAkB,EAAC;AAAA,QACvD,GAAI,QAAA,KAAa,MAAA,GAAY,EAAE,QAAA,KAAa,EAAC;AAAA,QAC7C,GAAI,MAAA,KAAW,MAAA,GAAY,EAAE,MAAA,KAAW;AAAC;AAAA,KAC5C,GACE,IAAA;AAAA,IACH,aAAa,IAAA,mBACZ,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,kBAAA;AAAA,QACV,aAAA,EAAY,MAAA;AAAA,QACX,GAAG,WAAA;AAAA,QAEJ,uBAAA,EAAyB,EAAE,MAAA,EAAQ,QAAA;AAAS;AAAA,KAC9C,GACE,QAAA,CAAS,IAAA,KAAS,MAAA,uBACnB,KAAA,EAAA,EAAI,SAAA,EAAU,kBAAA,EAAmB,aAAA,EAAY,MAAA,EAAQ,GAAG,WAAA,EACtD,QAAA,EAAA,QAAA,CAAS,MACZ,CAAA,GACE,IAAA;AAAA,IACH,QAAA,CAAS,WAAA,KAAgB,MAAA,mBACxB,GAAA,CAAC,OAAE,SAAA,EAAU,yBAAA,EAA2B,GAAG,WAAA,EACzC,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,QAAA,CAAS,WAAA,EAAY,GAC9B,CAAA,GACE;AAAA,GAAA,EACN,CAAA;AAEJ","file":"chunk-4ZZWZSWI.js","sourcesContent":["'use client';\n\nimport type { InteractionEvent, Stimulus } from '@intellectif/lk-core';\nimport { useId, useMemo } from 'react';\nimport { useLkStrings } from '../../i18n/LkIntlProvider.js';\nimport type { LkStringsOverride } from '../../i18n/strings.js';\nimport { ActivityMedia } from '../shared/ActivityMedia.js';\nimport type {\n HtmlSanitizer,\n MediaBudgetBinding,\n MediaTransportStrings,\n RenderMode,\n} from '../types.js';\n\nexport interface StimulusPanelProps {\n stimulus: Stimulus;\n /**\n * 1-based presented positions of the first and last question this material\n * serves, rendered as \"Questions 3–8\" so a learner knows how far the\n * passage carries.\n */\n range?: { first: number; last: number };\n /** Renders `stimulus.bodyHtml` when provided. See `HtmlSanitizer`. */\n sanitizeHtml?: HtmlSanitizer;\n locale?: string;\n /**\n * Forwarded to the stimulus's own media. `review` keeps the browser's control\n * bar even under an enforcing policy — a graded paper cannot be changed by\n * listening again.\n */\n renderMode?: RenderMode;\n /**\n * Binds this group's recording to a play budget the consumer persists. One\n * recording serves every question in the group, so the key is derived from\n * the ENTRY rather than the slot — see `stimulusMediaKey()` in lk-core.\n */\n mediaBudget?: MediaBudgetBinding;\n /** Translations for the audio transport chrome. */\n mediaStrings?: Partial<MediaTransportStrings>;\n onInteraction?: (event: InteractionEvent) => void;\n disabled?: boolean;\n /** Overrides the SDK's chrome text for this panel. See {@link LkIntlProvider}. */\n strings?: LkStringsOverride;\n}\n\n/**\n * Presents a shared stimulus — the passage, recording or image an item group's\n * questions refer to. A landmark region named by its title (or by its kind),\n * so a screen-reader user can jump back to the material from any question.\n *\n * Rich text follows the SDK-wide rule: `bodyHtml` is rendered only through a\n * caller-supplied sanitiser, and the plain `body` is the fallback — which the\n * schema guarantees exists whenever `bodyHtml` does.\n */\nexport function StimulusPanel({\n stimulus,\n range,\n sanitizeHtml,\n locale,\n renderMode,\n mediaBudget,\n mediaStrings,\n onInteraction,\n disabled,\n strings,\n}: StimulusPanelProps): React.JSX.Element {\n const titleId = useId();\n const s = useLkStrings(strings);\n\n const bodyHtml = useMemo(() => {\n if (sanitizeHtml === undefined || typeof stimulus.bodyHtml !== 'string') {\n return null;\n }\n return sanitizeHtml(stimulus.bodyHtml);\n }, [stimulus.bodyHtml, sanitizeHtml]);\n\n const rangeText = range === undefined ? null : s.stimulusRange(range.first, range.last);\n\n const naming =\n stimulus.title !== undefined\n ? { 'aria-labelledby': titleId }\n : { 'aria-label': s.stimulusKind[stimulus.kind] };\n\n // The AUTHORED parts carry the stimulus language; the SDK's own chrome does\n // not. Declaring `stimulus.locale` on the whole region made a screen reader\n // read \"Recording\" and \"Questions 3–5\" — English strings this package ships\n // — in the passage's voice, so a Spanish listening panel announced its own\n // name and the only orientation text in it with Spanish phonetics\n // (WCAG 3.1.2 Language of Parts). Scope the declaration to the content it\n // actually describes.\n const contentLang = stimulus.locale !== undefined ? { lang: stimulus.locale } : {};\n\n return (\n <section className=\"lk-stimulus\" data-kind={stimulus.kind} lang={locale} {...naming}>\n {stimulus.title !== undefined ? (\n <p className=\"lk-stimulus-title\" id={titleId} {...contentLang}>\n {stimulus.title}\n </p>\n ) : null}\n {rangeText !== null ? <p className=\"lk-stimulus-range\">{rangeText}</p> : null}\n {stimulus.media !== undefined ? (\n <ActivityMedia\n media={stimulus.media}\n {...(renderMode !== undefined ? { renderMode } : {})}\n {...(mediaBudget !== undefined ? { mediaBudget } : {})}\n {...(mediaStrings !== undefined ? { mediaStrings } : {})}\n {...(strings !== undefined ? { strings } : {})}\n {...(onInteraction !== undefined ? { onInteraction } : {})}\n {...(disabled !== undefined ? { disabled } : {})}\n {...(locale !== undefined ? { locale } : {})}\n />\n ) : null}\n {bodyHtml !== null ? (\n <div\n className=\"lk-stimulus-body\"\n data-format=\"html\"\n {...contentLang}\n // biome-ignore lint/security/noDangerouslySetInnerHtml: rendered only through the caller-supplied HtmlSanitizer — the SDK never injects unsanitised markup\n dangerouslySetInnerHTML={{ __html: bodyHtml }}\n />\n ) : stimulus.body !== undefined ? (\n <div className=\"lk-stimulus-body\" data-format=\"text\" {...contentLang}>\n {stimulus.body}\n </div>\n ) : null}\n {stimulus.attribution !== undefined ? (\n <p className=\"lk-stimulus-attribution\" {...contentLang}>\n <cite>{stimulus.attribution}</cite>\n </p>\n ) : null}\n </section>\n );\n}\n"]}
|